Compare commits
19 Commits
feature/es
...
master
Author | SHA1 | Date | |
---|---|---|---|
2333497adc | |||
3ba4d9d10d | |||
d8ffb99455 | |||
ff8966db5c | |||
e37ac9defd | |||
34ec983d80 | |||
e0375a1fb6 | |||
5c12efc75b | |||
ea4b7d415c | |||
50909fd3cf | |||
fef6d6acb4 | |||
e2bc8b4dc8 | |||
8890e2851c | |||
5b5da8b117 | |||
ab6ddd0f32 | |||
99ed8af3ff | |||
37a23ac99e | |||
ef9ccc6a14 | |||
49caf9e414 |
129
CODE_OF_CONDUCT.md
Normal file
129
CODE_OF_CONDUCT.md
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
|
||||||
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
We as members, contributors, and leaders pledge to make participation in our
|
||||||
|
community a harassment-free experience for everyone, regardless of age, body
|
||||||
|
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||||
|
identity and expression, level of experience, education, socio-economic status,
|
||||||
|
nationality, personal appearance, race, religion, or sexual identity
|
||||||
|
and orientation.
|
||||||
|
|
||||||
|
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||||
|
diverse, inclusive, and healthy community.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to a positive environment for our
|
||||||
|
community include:
|
||||||
|
|
||||||
|
* Demonstrating empathy and kindness toward other people
|
||||||
|
* Being respectful of differing opinions, viewpoints, and experiences
|
||||||
|
* Giving and gracefully accepting constructive feedback
|
||||||
|
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||||
|
and learning from the experience
|
||||||
|
* Focusing on what is best not just for us as individuals, but for the
|
||||||
|
overall community
|
||||||
|
|
||||||
|
Examples of unacceptable behavior include:
|
||||||
|
|
||||||
|
* The use of sexualized language or imagery, and sexual attention or
|
||||||
|
advances of any kind
|
||||||
|
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||||
|
* Public or private harassment
|
||||||
|
* Publishing others' private information, such as a physical or email
|
||||||
|
address, without their explicit permission
|
||||||
|
* Other conduct which could reasonably be considered inappropriate in a
|
||||||
|
professional setting
|
||||||
|
|
||||||
|
## Enforcement Responsibilities
|
||||||
|
|
||||||
|
Community leaders are responsible for clarifying and enforcing our standards of
|
||||||
|
acceptable behavior and will take appropriate and fair corrective action in
|
||||||
|
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||||
|
or harmful.
|
||||||
|
|
||||||
|
Community leaders have the right and responsibility to remove, edit, or reject
|
||||||
|
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||||
|
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||||
|
decisions when appropriate.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies within all community spaces, and also applies when
|
||||||
|
an individual is officially representing the community in public spaces.
|
||||||
|
Examples of representing our community include using an official e-mail address,
|
||||||
|
posting via an official social media account, or acting as an appointed
|
||||||
|
representative at an online or offline event.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
|
reported to the community leaders responsible for enforcement at
|
||||||
|
https://sidweb.nl/cms3/en/contact.
|
||||||
|
All complaints will be reviewed and investigated promptly and fairly.
|
||||||
|
|
||||||
|
All community leaders are obligated to respect the privacy and security of the
|
||||||
|
reporter of any incident.
|
||||||
|
|
||||||
|
## Enforcement Guidelines
|
||||||
|
|
||||||
|
Community leaders will follow these Community Impact Guidelines in determining
|
||||||
|
the consequences for any action they deem in violation of this Code of Conduct:
|
||||||
|
|
||||||
|
### 1. Correction
|
||||||
|
|
||||||
|
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||||
|
unprofessional or unwelcome in the community.
|
||||||
|
|
||||||
|
**Consequence**: A private, written warning from community leaders, providing
|
||||||
|
clarity around the nature of the violation and an explanation of why the
|
||||||
|
behavior was inappropriate. A public apology may be requested.
|
||||||
|
|
||||||
|
### 2. Warning
|
||||||
|
|
||||||
|
**Community Impact**: A violation through a single incident or series
|
||||||
|
of actions.
|
||||||
|
|
||||||
|
**Consequence**: A warning with consequences for continued behavior. No
|
||||||
|
interaction with the people involved, including unsolicited interaction with
|
||||||
|
those enforcing the Code of Conduct, for a specified period of time. This
|
||||||
|
includes avoiding interactions in community spaces as well as external channels
|
||||||
|
like social media. Violating these terms may lead to a temporary or
|
||||||
|
permanent ban.
|
||||||
|
|
||||||
|
### 3. Temporary Ban
|
||||||
|
|
||||||
|
**Community Impact**: A serious violation of community standards, including
|
||||||
|
sustained inappropriate behavior.
|
||||||
|
|
||||||
|
**Consequence**: A temporary ban from any sort of interaction or public
|
||||||
|
communication with the community for a specified period of time. No public or
|
||||||
|
private interaction with the people involved, including unsolicited interaction
|
||||||
|
with those enforcing the Code of Conduct, is allowed during this period.
|
||||||
|
Violating these terms may lead to a permanent ban.
|
||||||
|
|
||||||
|
### 4. Permanent Ban
|
||||||
|
|
||||||
|
**Community Impact**: Demonstrating a pattern of violation of community
|
||||||
|
standards, including sustained inappropriate behavior, harassment of an
|
||||||
|
individual, or aggression toward or disparagement of classes of individuals.
|
||||||
|
|
||||||
|
**Consequence**: A permanent ban from any sort of public interaction within
|
||||||
|
the community.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||||
|
version 2.0, available at
|
||||||
|
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||||
|
|
||||||
|
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||||
|
enforcement ladder](https://github.com/mozilla/diversity).
|
||||||
|
|
||||||
|
[homepage]: https://www.contributor-covenant.org
|
||||||
|
|
||||||
|
For answers to common questions about this code of conduct, see the FAQ at
|
||||||
|
https://www.contributor-covenant.org/faq. Translations are available at
|
||||||
|
https://www.contributor-covenant.org/translations.
|
165
LICENSE
Normal file
165
LICENSE
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
GNU LESSER GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
|
||||||
|
This version of the GNU Lesser General Public License incorporates
|
||||||
|
the terms and conditions of version 3 of the GNU General Public
|
||||||
|
License, supplemented by the additional permissions listed below.
|
||||||
|
|
||||||
|
0. Additional Definitions.
|
||||||
|
|
||||||
|
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||||
|
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||||
|
General Public License.
|
||||||
|
|
||||||
|
"The Library" refers to a covered work governed by this License,
|
||||||
|
other than an Application or a Combined Work as defined below.
|
||||||
|
|
||||||
|
An "Application" is any work that makes use of an interface provided
|
||||||
|
by the Library, but which is not otherwise based on the Library.
|
||||||
|
Defining a subclass of a class defined by the Library is deemed a mode
|
||||||
|
of using an interface provided by the Library.
|
||||||
|
|
||||||
|
A "Combined Work" is a work produced by combining or linking an
|
||||||
|
Application with the Library. The particular version of the Library
|
||||||
|
with which the Combined Work was made is also called the "Linked
|
||||||
|
Version".
|
||||||
|
|
||||||
|
The "Minimal Corresponding Source" for a Combined Work means the
|
||||||
|
Corresponding Source for the Combined Work, excluding any source code
|
||||||
|
for portions of the Combined Work that, considered in isolation, are
|
||||||
|
based on the Application, and not on the Linked Version.
|
||||||
|
|
||||||
|
The "Corresponding Application Code" for a Combined Work means the
|
||||||
|
object code and/or source code for the Application, including any data
|
||||||
|
and utility programs needed for reproducing the Combined Work from the
|
||||||
|
Application, but excluding the System Libraries of the Combined Work.
|
||||||
|
|
||||||
|
1. Exception to Section 3 of the GNU GPL.
|
||||||
|
|
||||||
|
You may convey a covered work under sections 3 and 4 of this License
|
||||||
|
without being bound by section 3 of the GNU GPL.
|
||||||
|
|
||||||
|
2. Conveying Modified Versions.
|
||||||
|
|
||||||
|
If you modify a copy of the Library, and, in your modifications, a
|
||||||
|
facility refers to a function or data to be supplied by an Application
|
||||||
|
that uses the facility (other than as an argument passed when the
|
||||||
|
facility is invoked), then you may convey a copy of the modified
|
||||||
|
version:
|
||||||
|
|
||||||
|
a) under this License, provided that you make a good faith effort to
|
||||||
|
ensure that, in the event an Application does not supply the
|
||||||
|
function or data, the facility still operates, and performs
|
||||||
|
whatever part of its purpose remains meaningful, or
|
||||||
|
|
||||||
|
b) under the GNU GPL, with none of the additional permissions of
|
||||||
|
this License applicable to that copy.
|
||||||
|
|
||||||
|
3. Object Code Incorporating Material from Library Header Files.
|
||||||
|
|
||||||
|
The object code form of an Application may incorporate material from
|
||||||
|
a header file that is part of the Library. You may convey such object
|
||||||
|
code under terms of your choice, provided that, if the incorporated
|
||||||
|
material is not limited to numerical parameters, data structure
|
||||||
|
layouts and accessors, or small macros, inline functions and templates
|
||||||
|
(ten or fewer lines in length), you do both of the following:
|
||||||
|
|
||||||
|
a) Give prominent notice with each copy of the object code that the
|
||||||
|
Library is used in it and that the Library and its use are
|
||||||
|
covered by this License.
|
||||||
|
|
||||||
|
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||||
|
document.
|
||||||
|
|
||||||
|
4. Combined Works.
|
||||||
|
|
||||||
|
You may convey a Combined Work under terms of your choice that,
|
||||||
|
taken together, effectively do not restrict modification of the
|
||||||
|
portions of the Library contained in the Combined Work and reverse
|
||||||
|
engineering for debugging such modifications, if you also do each of
|
||||||
|
the following:
|
||||||
|
|
||||||
|
a) Give prominent notice with each copy of the Combined Work that
|
||||||
|
the Library is used in it and that the Library and its use are
|
||||||
|
covered by this License.
|
||||||
|
|
||||||
|
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||||
|
document.
|
||||||
|
|
||||||
|
c) For a Combined Work that displays copyright notices during
|
||||||
|
execution, include the copyright notice for the Library among
|
||||||
|
these notices, as well as a reference directing the user to the
|
||||||
|
copies of the GNU GPL and this license document.
|
||||||
|
|
||||||
|
d) Do one of the following:
|
||||||
|
|
||||||
|
0) Convey the Minimal Corresponding Source under the terms of this
|
||||||
|
License, and the Corresponding Application Code in a form
|
||||||
|
suitable for, and under terms that permit, the user to
|
||||||
|
recombine or relink the Application with a modified version of
|
||||||
|
the Linked Version to produce a modified Combined Work, in the
|
||||||
|
manner specified by section 6 of the GNU GPL for conveying
|
||||||
|
Corresponding Source.
|
||||||
|
|
||||||
|
1) Use a suitable shared library mechanism for linking with the
|
||||||
|
Library. A suitable mechanism is one that (a) uses at run time
|
||||||
|
a copy of the Library already present on the user's computer
|
||||||
|
system, and (b) will operate properly with a modified version
|
||||||
|
of the Library that is interface-compatible with the Linked
|
||||||
|
Version.
|
||||||
|
|
||||||
|
e) Provide Installation Information, but only if you would otherwise
|
||||||
|
be required to provide such information under section 6 of the
|
||||||
|
GNU GPL, and only to the extent that such information is
|
||||||
|
necessary to install and execute a modified version of the
|
||||||
|
Combined Work produced by recombining or relinking the
|
||||||
|
Application with a modified version of the Linked Version. (If
|
||||||
|
you use option 4d0, the Installation Information must accompany
|
||||||
|
the Minimal Corresponding Source and Corresponding Application
|
||||||
|
Code. If you use option 4d1, you must provide the Installation
|
||||||
|
Information in the manner specified by section 6 of the GNU GPL
|
||||||
|
for conveying Corresponding Source.)
|
||||||
|
|
||||||
|
5. Combined Libraries.
|
||||||
|
|
||||||
|
You may place library facilities that are a work based on the
|
||||||
|
Library side by side in a single library together with other library
|
||||||
|
facilities that are not Applications and are not covered by this
|
||||||
|
License, and convey such a combined library under terms of your
|
||||||
|
choice, if you do both of the following:
|
||||||
|
|
||||||
|
a) Accompany the combined library with a copy of the same work based
|
||||||
|
on the Library, uncombined with any other library facilities,
|
||||||
|
conveyed under the terms of this License.
|
||||||
|
|
||||||
|
b) Give prominent notice with the combined library that part of it
|
||||||
|
is a work based on the Library, and explaining where to find the
|
||||||
|
accompanying uncombined form of the same work.
|
||||||
|
|
||||||
|
6. Revised Versions of the GNU Lesser General Public License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions
|
||||||
|
of the GNU Lesser General Public License from time to time. Such new
|
||||||
|
versions will be similar in spirit to the present version, but may
|
||||||
|
differ in detail to address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the
|
||||||
|
Library as you received it specifies that a certain numbered version
|
||||||
|
of the GNU Lesser General Public License "or any later version"
|
||||||
|
applies to it, you have the option of following the terms and
|
||||||
|
conditions either of that published version or of any later version
|
||||||
|
published by the Free Software Foundation. If the Library as you
|
||||||
|
received it does not specify a version number of the GNU Lesser
|
||||||
|
General Public License, you may choose any version of the GNU Lesser
|
||||||
|
General Public License ever published by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Library as you received it specifies that a proxy can decide
|
||||||
|
whether future versions of the GNU Lesser General Public License shall
|
||||||
|
apply, that proxy's public statement of acceptance of any version is
|
||||||
|
permanent authorization for you to choose that version for the
|
||||||
|
Library.
|
@ -1 +0,0 @@
|
|||||||
theme: jekyll-theme-cayman
|
|
@ -1,3 +0,0 @@
|
|||||||
COMPONENT_ADD_INCLUDEDIRS := src
|
|
||||||
COMPONENT_SRCDIRS := src
|
|
||||||
CXXFLAGS += -fno-rtti
|
|
8
docs/_config.yml
Normal file
8
docs/_config.yml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# bundle exec jekyll serve --host=0.0.0.0
|
||||||
|
|
||||||
|
title: ESPAsyncWebServer
|
||||||
|
description: "Asynchronous HTTP and WebSocket Server Library for ESP32, ESP8266 and RP2040"
|
||||||
|
remote_theme: pages-themes/cayman@v0.2.0
|
||||||
|
plugins:
|
||||||
|
- jekyll-remote-theme
|
||||||
|
|
1885
docs/index.md
Normal file
1885
docs/index.md
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,10 +1,13 @@
|
|||||||
#include <DNSServer.h>
|
#include <DNSServer.h>
|
||||||
#ifdef ESP32
|
#ifdef ESP32
|
||||||
#include <WiFi.h>
|
#include <AsyncTCP.h>
|
||||||
#include <AsyncTCP.h>
|
#include <WiFi.h>
|
||||||
#elif defined(ESP8266)
|
#elif defined(ESP8266)
|
||||||
#include <ESP8266WiFi.h>
|
#include <ESP8266WiFi.h>
|
||||||
#include <ESPAsyncTCP.h>
|
#include <ESPAsyncTCP.h>
|
||||||
|
#elif defined(TARGET_RP2040)
|
||||||
|
#include <WebServer.h>
|
||||||
|
#include <WiFi.h>
|
||||||
#endif
|
#endif
|
||||||
#include "ESPAsyncWebServer.h"
|
#include "ESPAsyncWebServer.h"
|
||||||
|
|
||||||
@ -12,36 +15,47 @@ DNSServer dnsServer;
|
|||||||
AsyncWebServer server(80);
|
AsyncWebServer server(80);
|
||||||
|
|
||||||
class CaptiveRequestHandler : public AsyncWebHandler {
|
class CaptiveRequestHandler : public AsyncWebHandler {
|
||||||
public:
|
public:
|
||||||
CaptiveRequestHandler() {}
|
CaptiveRequestHandler() {}
|
||||||
virtual ~CaptiveRequestHandler() {}
|
virtual ~CaptiveRequestHandler() {}
|
||||||
|
|
||||||
bool canHandle(AsyncWebServerRequest *request){
|
bool canHandle(__unused AsyncWebServerRequest* request) {
|
||||||
//request->addInterestingHeader("ANY");
|
return true;
|
||||||
return true;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void handleRequest(AsyncWebServerRequest *request) {
|
void handleRequest(AsyncWebServerRequest* request) {
|
||||||
AsyncResponseStream *response = request->beginResponseStream("text/html");
|
AsyncResponseStream* response = request->beginResponseStream("text/html");
|
||||||
response->print("<!DOCTYPE html><html><head><title>Captive Portal</title></head><body>");
|
response->print("<!DOCTYPE html><html><head><title>Captive Portal</title></head><body>");
|
||||||
response->print("<p>This is out captive portal front page.</p>");
|
response->print("<p>This is out captive portal front page.</p>");
|
||||||
response->printf("<p>You were trying to reach: http://%s%s</p>", request->host().c_str(), request->url().c_str());
|
response->printf("<p>You were trying to reach: http://%s%s</p>", request->host().c_str(), request->url().c_str());
|
||||||
response->printf("<p>Try opening <a href='http://%s'>this link</a> instead</p>", WiFi.softAPIP().toString().c_str());
|
#ifndef CONFIG_IDF_TARGET_ESP32H2
|
||||||
response->print("</body></html>");
|
response->printf("<p>Try opening <a href='http://%s'>this link</a> instead</p>", WiFi.softAPIP().toString().c_str());
|
||||||
request->send(response);
|
#endif
|
||||||
}
|
response->print("</body></html>");
|
||||||
|
request->send(response);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(115200);
|
||||||
|
Serial.println();
|
||||||
|
Serial.println("Configuring access point...");
|
||||||
|
|
||||||
|
#ifndef CONFIG_IDF_TARGET_ESP32H2
|
||||||
|
if (!WiFi.softAP("esp-captive")) {
|
||||||
|
Serial.println("Soft AP creation failed.");
|
||||||
|
while (1)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
void setup(){
|
|
||||||
//your other setup stuff...
|
|
||||||
WiFi.softAP("esp-captive");
|
|
||||||
dnsServer.start(53, "*", WiFi.softAPIP());
|
dnsServer.start(53, "*", WiFi.softAPIP());
|
||||||
server.addHandler(new CaptiveRequestHandler()).setFilter(ON_AP_FILTER);//only when requested from AP
|
#endif
|
||||||
//more handlers...
|
|
||||||
|
server.addHandler(new CaptiveRequestHandler()).setFilter(ON_AP_FILTER); // only when requested from AP
|
||||||
|
// more handlers...
|
||||||
server.begin();
|
server.begin();
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop(){
|
void loop() {
|
||||||
dnsServer.processNextRequest();
|
dnsServer.processNextRequest();
|
||||||
}
|
}
|
||||||
|
37
examples/Draft/Draft.ino
Normal file
37
examples/Draft/Draft.ino
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
#include "mbedtls/md5.h"
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <MD5Builder.h>
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(115200);
|
||||||
|
delay(2000);
|
||||||
|
|
||||||
|
const char* data = "Hello World";
|
||||||
|
|
||||||
|
{
|
||||||
|
uint8_t md5[16];
|
||||||
|
mbedtls_md5_context _ctx;
|
||||||
|
mbedtls_md5_init(&_ctx);
|
||||||
|
mbedtls_md5_starts(&_ctx);
|
||||||
|
mbedtls_md5_update(&_ctx, (const unsigned char*)data, strlen(data));
|
||||||
|
mbedtls_md5_finish(&_ctx, md5);
|
||||||
|
char output[33];
|
||||||
|
for (int i = 0; i < 16; i++) {
|
||||||
|
sprintf_P(output + (i * 2), PSTR("%02x"), md5[i]);
|
||||||
|
}
|
||||||
|
Serial.println(String(output));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
MD5Builder md5;
|
||||||
|
md5.begin();
|
||||||
|
md5.add(data, strlen(data);
|
||||||
|
md5.calculate();
|
||||||
|
char output[33];
|
||||||
|
md5.getChars(output);
|
||||||
|
Serial.println(String(output));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
}
|
127
examples/Filters/Filters.ino
Normal file
127
examples/Filters/Filters.ino
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
// Reproduced issue https://github.com/mathieucarbou/ESPAsyncWebServer/issues/26
|
||||||
|
|
||||||
|
#include <DNSServer.h>
|
||||||
|
#ifdef ESP32
|
||||||
|
#include <AsyncTCP.h>
|
||||||
|
#include <WiFi.h>
|
||||||
|
#elif defined(ESP8266)
|
||||||
|
#include <ESP8266WiFi.h>
|
||||||
|
#include <ESPAsyncTCP.h>
|
||||||
|
#elif defined(TARGET_RP2040)
|
||||||
|
#include <WebServer.h>
|
||||||
|
#include <WiFi.h>
|
||||||
|
#endif
|
||||||
|
#include "ESPAsyncWebServer.h"
|
||||||
|
|
||||||
|
DNSServer dnsServer;
|
||||||
|
AsyncWebServer server(80);
|
||||||
|
|
||||||
|
class CaptiveRequestHandler : public AsyncWebHandler {
|
||||||
|
public:
|
||||||
|
CaptiveRequestHandler() {}
|
||||||
|
virtual ~CaptiveRequestHandler() {}
|
||||||
|
|
||||||
|
bool canHandle(__unused AsyncWebServerRequest* request) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleRequest(AsyncWebServerRequest* request) {
|
||||||
|
AsyncResponseStream* response = request->beginResponseStream("text/html");
|
||||||
|
response->print("<!DOCTYPE html><html><head><title>Captive Portal</title></head><body>");
|
||||||
|
response->print("<p>This is out captive portal front page.</p>");
|
||||||
|
response->printf("<p>You were trying to reach: http://%s%s</p>", request->host().c_str(), request->url().c_str());
|
||||||
|
#ifndef CONFIG_IDF_TARGET_ESP32H2
|
||||||
|
response->printf("<p>Try opening <a href='http://%s'>this link</a> instead</p>", WiFi.softAPIP().toString().c_str());
|
||||||
|
#endif
|
||||||
|
response->print("</body></html>");
|
||||||
|
request->send(response);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
bool hit1 = false;
|
||||||
|
bool hit2 = false;
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(115200);
|
||||||
|
|
||||||
|
server
|
||||||
|
.on("/", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||||
|
Serial.println("Captive portal request...");
|
||||||
|
#ifndef CONFIG_IDF_TARGET_ESP32H2
|
||||||
|
Serial.println("WiFi.localIP(): " + WiFi.localIP().toString());
|
||||||
|
#endif
|
||||||
|
Serial.println("request->client()->localIP(): " + request->client()->localIP().toString());
|
||||||
|
#if ESP_IDF_VERSION_MAJOR >= 5
|
||||||
|
#ifndef CONFIG_IDF_TARGET_ESP32H2
|
||||||
|
Serial.println("WiFi.type(): " + String((int)WiFi.localIP().type()));
|
||||||
|
#endif
|
||||||
|
Serial.println("request->client()->type(): " + String((int)request->client()->localIP().type()));
|
||||||
|
#endif
|
||||||
|
#ifndef CONFIG_IDF_TARGET_ESP32H2
|
||||||
|
Serial.println(WiFi.localIP() == request->client()->localIP() ? "should be: ON_STA_FILTER" : "should be: ON_AP_FILTER");
|
||||||
|
Serial.println(WiFi.localIP() == request->client()->localIP());
|
||||||
|
Serial.println(WiFi.localIP().toString() == request->client()->localIP().toString());
|
||||||
|
#endif
|
||||||
|
request->send(200, "text/plain", "This is the captive portal");
|
||||||
|
hit1 = true;
|
||||||
|
})
|
||||||
|
.setFilter(ON_AP_FILTER);
|
||||||
|
|
||||||
|
server
|
||||||
|
.on("/", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||||
|
Serial.println("Website request...");
|
||||||
|
#ifndef CONFIG_IDF_TARGET_ESP32H2
|
||||||
|
Serial.println("WiFi.localIP(): " + WiFi.localIP().toString());
|
||||||
|
#endif
|
||||||
|
Serial.println("request->client()->localIP(): " + request->client()->localIP().toString());
|
||||||
|
#if ESP_IDF_VERSION_MAJOR >= 5
|
||||||
|
#ifndef CONFIG_IDF_TARGET_ESP32H2
|
||||||
|
Serial.println("WiFi.type(): " + String((int)WiFi.localIP().type()));
|
||||||
|
#endif
|
||||||
|
Serial.println("request->client()->type(): " + String((int)request->client()->localIP().type()));
|
||||||
|
#endif
|
||||||
|
#ifndef CONFIG_IDF_TARGET_ESP32H2
|
||||||
|
Serial.println(WiFi.localIP() == request->client()->localIP() ? "should be: ON_STA_FILTER" : "should be: ON_AP_FILTER");
|
||||||
|
Serial.println(WiFi.localIP() == request->client()->localIP());
|
||||||
|
Serial.println(WiFi.localIP().toString() == request->client()->localIP().toString());
|
||||||
|
#endif
|
||||||
|
request->send(200, "text/plain", "This is the website");
|
||||||
|
hit2 = true;
|
||||||
|
})
|
||||||
|
.setFilter(ON_STA_FILTER);
|
||||||
|
|
||||||
|
// assert(WiFi.softAP("esp-captive-portal"));
|
||||||
|
// dnsServer.start(53, "*", WiFi.softAPIP());
|
||||||
|
// server.begin();
|
||||||
|
// Serial.println("Captive portal started!");
|
||||||
|
|
||||||
|
// while (!hit1) {
|
||||||
|
// dnsServer.processNextRequest();
|
||||||
|
// yield();
|
||||||
|
// }
|
||||||
|
// delay(1000); // Wait for the client to process the response
|
||||||
|
|
||||||
|
// Serial.println("Captive portal opened, stopping it and connecting to WiFi...");
|
||||||
|
// dnsServer.stop();
|
||||||
|
// WiFi.softAPdisconnect();
|
||||||
|
|
||||||
|
#ifndef CONFIG_IDF_TARGET_ESP32H2
|
||||||
|
WiFi.persistent(false);
|
||||||
|
WiFi.begin("IoT");
|
||||||
|
while (WiFi.status() != WL_CONNECTED) {
|
||||||
|
delay(500);
|
||||||
|
}
|
||||||
|
Serial.println("Connected to WiFi with IP address: " + WiFi.localIP().toString());
|
||||||
|
#endif
|
||||||
|
|
||||||
|
server.begin();
|
||||||
|
|
||||||
|
// while (!hit2) {
|
||||||
|
// delay(10);
|
||||||
|
// }
|
||||||
|
// delay(1000); // Wait for the client to process the response
|
||||||
|
// ESP.restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
}
|
472
examples/SimpleServer/SimpleServer.ino
Normal file
472
examples/SimpleServer/SimpleServer.ino
Normal file
@ -0,0 +1,472 @@
|
|||||||
|
//
|
||||||
|
// A simple server implementation showing how to:
|
||||||
|
// * serve static messages
|
||||||
|
// * read GET and POST parameters
|
||||||
|
// * handle missing pages / 404s
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#ifdef ESP32
|
||||||
|
#include <AsyncTCP.h>
|
||||||
|
#include <WiFi.h>
|
||||||
|
#elif defined(ESP8266)
|
||||||
|
#include <ESP8266WiFi.h>
|
||||||
|
#include <ESPAsyncTCP.h>
|
||||||
|
#elif defined(TARGET_RP2040)
|
||||||
|
#include <WebServer.h>
|
||||||
|
#include <WiFi.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <ESPAsyncWebServer.h>
|
||||||
|
|
||||||
|
#if ASYNC_JSON_SUPPORT == 1
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
#include <AsyncJson.h>
|
||||||
|
#include <AsyncMessagePack.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <LittleFS.h>
|
||||||
|
|
||||||
|
AsyncWebServer server(80);
|
||||||
|
AsyncEventSource events("/events");
|
||||||
|
AsyncWebSocket ws("/ws");
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Middlewares
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// log incoming requests
|
||||||
|
LoggingMiddleware requestLogger;
|
||||||
|
|
||||||
|
// CORS
|
||||||
|
CorsMiddleware cors;
|
||||||
|
|
||||||
|
// maximum 5 requests per 10 seconds
|
||||||
|
RateLimitMiddleware rateLimit;
|
||||||
|
|
||||||
|
// filter out specific headers from the incoming request
|
||||||
|
HeaderFilterMiddleware headerFilter;
|
||||||
|
|
||||||
|
// remove all headers from the incoming request except the ones provided in the constructor
|
||||||
|
HeaderFreeMiddleware headerFree;
|
||||||
|
|
||||||
|
// basicAuth
|
||||||
|
AuthenticationMiddleware basicAuth;
|
||||||
|
AuthenticationMiddleware basicAuthHash;
|
||||||
|
|
||||||
|
// simple digest authentication
|
||||||
|
AuthenticationMiddleware digestAuth;
|
||||||
|
AuthenticationMiddleware digestAuthHash;
|
||||||
|
|
||||||
|
// complex authentication which adds request attributes for the next middlewares and handler
|
||||||
|
AsyncMiddlewareFunction complexAuth([](AsyncWebServerRequest* request, ArMiddlewareNext next) {
|
||||||
|
if (!request->authenticate("user", "password")) {
|
||||||
|
return request->requestAuthentication();
|
||||||
|
}
|
||||||
|
request->setAttribute("user", "Mathieu");
|
||||||
|
request->setAttribute("role", "staff");
|
||||||
|
|
||||||
|
next();
|
||||||
|
|
||||||
|
request->getResponse()->addHeader("X-Rate-Limit", "200");
|
||||||
|
});
|
||||||
|
|
||||||
|
AuthorizationMiddleware authz([](AsyncWebServerRequest* request) { return request->getAttribute("role") == "staff"; });
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
const char* PARAM_MESSAGE PROGMEM = "message";
|
||||||
|
const char* SSE_HTLM PROGMEM = R"(
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Server-Sent Events</title>
|
||||||
|
<script>
|
||||||
|
if (!!window.EventSource) {
|
||||||
|
var source = new EventSource('/events');
|
||||||
|
source.addEventListener('open', function(e) {
|
||||||
|
console.log("Events Connected");
|
||||||
|
}, false);
|
||||||
|
source.addEventListener('error', function(e) {
|
||||||
|
if (e.target.readyState != EventSource.OPEN) {
|
||||||
|
console.log("Events Disconnected");
|
||||||
|
}
|
||||||
|
}, false);
|
||||||
|
source.addEventListener('message', function(e) {
|
||||||
|
console.log("message", e.data);
|
||||||
|
}, false);
|
||||||
|
source.addEventListener('heartbeat', function(e) {
|
||||||
|
console.log("heartbeat", e.data);
|
||||||
|
}, false);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Open your browser console!</h1>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
)";
|
||||||
|
|
||||||
|
void notFound(AsyncWebServerRequest* request) {
|
||||||
|
request->send(404, "text/plain", "Not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
#if ASYNC_JSON_SUPPORT == 1
|
||||||
|
AsyncCallbackJsonWebHandler* jsonHandler = new AsyncCallbackJsonWebHandler("/json2");
|
||||||
|
AsyncCallbackMessagePackWebHandler* msgPackHandler = new AsyncCallbackMessagePackWebHandler("/msgpack2");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
|
||||||
|
Serial.begin(115200);
|
||||||
|
|
||||||
|
#ifndef CONFIG_IDF_TARGET_ESP32H2
|
||||||
|
// WiFi.mode(WIFI_STA);
|
||||||
|
// WiFi.begin("YOUR_SSID", "YOUR_PASSWORD");
|
||||||
|
// if (WiFi.waitForConnectResult() != WL_CONNECTED) {
|
||||||
|
// Serial.printf("WiFi Failed!\n");
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// Serial.print("IP Address: ");
|
||||||
|
// Serial.println(WiFi.localIP());
|
||||||
|
|
||||||
|
WiFi.mode(WIFI_AP);
|
||||||
|
WiFi.softAP("esp-captive");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// curl -v -X GET http://192.168.4.1/handler-not-sending-response
|
||||||
|
server.on("/handler-not-sending-response", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||||
|
// handler forgot to send a response to the client => 501 Not Implemented
|
||||||
|
});
|
||||||
|
|
||||||
|
// This is possible to replace a response.
|
||||||
|
// the previous one will be deleted.
|
||||||
|
// response sending happens when the handler returns.
|
||||||
|
// curl -v -X GET http://192.168.4.1/replace
|
||||||
|
server.on("/replace", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||||
|
request->send(200, "text/plain", "Hello, world");
|
||||||
|
// oups! finally we want to send a different response
|
||||||
|
request->send(400, "text/plain", "validation error");
|
||||||
|
#ifndef TARGET_RP2040
|
||||||
|
Serial.printf("Free heap: %" PRIu32 "\n", ESP.getFreeHeap());
|
||||||
|
#endif
|
||||||
|
});
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////
|
||||||
|
// Request header manipulations
|
||||||
|
///////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// curl -v -X GET -H "x-remove-me: value" http://192.168.4.1/headers
|
||||||
|
server.on("/headers", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||||
|
Serial.printf("Request Headers:\n");
|
||||||
|
for (auto& h : request->getHeaders())
|
||||||
|
Serial.printf("Request Header: %s = %s\n", h.name().c_str(), h.value().c_str());
|
||||||
|
|
||||||
|
// remove x-remove-me header
|
||||||
|
request->removeHeader("x-remove-me");
|
||||||
|
Serial.printf("Request Headers:\n");
|
||||||
|
for (auto& h : request->getHeaders())
|
||||||
|
Serial.printf("Request Header: %s = %s\n", h.name().c_str(), h.value().c_str());
|
||||||
|
|
||||||
|
std::vector<const char*> headers;
|
||||||
|
request->getHeaderNames(headers);
|
||||||
|
for (auto& h : headers)
|
||||||
|
Serial.printf("Request Header Name: %s\n", h);
|
||||||
|
|
||||||
|
request->send(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////
|
||||||
|
// Middlewares at server level (will apply to all requests)
|
||||||
|
///////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
requestLogger.setOutput(Serial);
|
||||||
|
|
||||||
|
basicAuth.setUsername("admin");
|
||||||
|
basicAuth.setPassword("admin");
|
||||||
|
basicAuth.setRealm("MyApp");
|
||||||
|
basicAuth.setAuthFailureMessage("Authentication failed");
|
||||||
|
basicAuth.setAuthType(AsyncAuthType::AUTH_BASIC);
|
||||||
|
basicAuth.generateHash();
|
||||||
|
|
||||||
|
basicAuthHash.setUsername("admin");
|
||||||
|
basicAuthHash.setPasswordHash("YWRtaW46YWRtaW4="); // BASE64(admin:admin)
|
||||||
|
basicAuthHash.setRealm("MyApp");
|
||||||
|
basicAuthHash.setAuthFailureMessage("Authentication failed");
|
||||||
|
basicAuthHash.setAuthType(AsyncAuthType::AUTH_BASIC);
|
||||||
|
|
||||||
|
digestAuth.setUsername("admin");
|
||||||
|
digestAuth.setPassword("admin");
|
||||||
|
digestAuth.setRealm("MyApp");
|
||||||
|
digestAuth.setAuthFailureMessage("Authentication failed");
|
||||||
|
digestAuth.setAuthType(AsyncAuthType::AUTH_DIGEST);
|
||||||
|
digestAuth.generateHash();
|
||||||
|
|
||||||
|
digestAuthHash.setUsername("admin");
|
||||||
|
digestAuthHash.setPasswordHash("f499b71f9a36d838b79268e145e132f7"); // MD5(user:realm:pass)
|
||||||
|
digestAuthHash.setRealm("MyApp");
|
||||||
|
digestAuthHash.setAuthFailureMessage("Authentication failed");
|
||||||
|
digestAuthHash.setAuthType(AsyncAuthType::AUTH_DIGEST);
|
||||||
|
|
||||||
|
rateLimit.setMaxRequests(5);
|
||||||
|
rateLimit.setWindowSize(10);
|
||||||
|
|
||||||
|
headerFilter.filter("X-Remove-Me");
|
||||||
|
headerFree.keep("X-Keep-Me");
|
||||||
|
headerFree.keep("host");
|
||||||
|
|
||||||
|
// global middleware
|
||||||
|
server.addMiddleware(&requestLogger);
|
||||||
|
server.addMiddlewares({&rateLimit, &cors, &headerFilter});
|
||||||
|
|
||||||
|
cors.setOrigin("http://192.168.4.1");
|
||||||
|
cors.setMethods("POST, GET, OPTIONS, DELETE");
|
||||||
|
cors.setHeaders("X-Custom-Header");
|
||||||
|
cors.setAllowCredentials(false);
|
||||||
|
cors.setMaxAge(600);
|
||||||
|
|
||||||
|
// Test CORS preflight request
|
||||||
|
// curl -v -X OPTIONS -H "origin: http://192.168.4.1" http://192.168.4.1/middleware/cors
|
||||||
|
server.on("/middleware/cors", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||||
|
request->send(200, "text/plain", "Hello, world!");
|
||||||
|
});
|
||||||
|
|
||||||
|
// curl -v -X GET -H "x-remove-me: value" http://192.168.4.1/middleware/test-header-filter
|
||||||
|
// - requestLogger will log the incoming headers (including x-remove-me)
|
||||||
|
// - headerFilter will remove x-remove-me header
|
||||||
|
// - handler will log the remaining headers
|
||||||
|
server.on("/middleware/test-header-filter", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||||
|
for (auto& h : request->getHeaders())
|
||||||
|
Serial.printf("Request Header: %s = %s\n", h.name().c_str(), h.value().c_str());
|
||||||
|
request->send(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
// curl -v -X GET -H "x-keep-me: value" http://192.168.4.1/middleware/test-header-free
|
||||||
|
// - requestLogger will log the incoming headers (including x-keep-me)
|
||||||
|
// - headerFree will remove all headers except x-keep-me and host
|
||||||
|
// - handler will log the remaining headers (x-keep-me and host)
|
||||||
|
server.on("/middleware/test-header-free", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||||
|
for (auto& h : request->getHeaders())
|
||||||
|
Serial.printf("Request Header: %s = %s\n", h.name().c_str(), h.value().c_str());
|
||||||
|
request->send(200);
|
||||||
|
})
|
||||||
|
.addMiddleware(&headerFree);
|
||||||
|
|
||||||
|
// basic authentication method
|
||||||
|
// curl -v -X GET -H "origin: http://192.168.4.1" -u admin:admin http://192.168.4.1/middleware/auth-basic
|
||||||
|
server.on("/middleware/auth-basic", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||||
|
request->send(200, "text/plain", "Hello, world!");
|
||||||
|
})
|
||||||
|
.addMiddleware(&basicAuth);
|
||||||
|
|
||||||
|
// basic authentication method with hash
|
||||||
|
// curl -v -X GET -H "origin: http://192.168.4.1" -u admin:admin http://192.168.4.1/middleware/auth-basic-hash
|
||||||
|
server.on("/middleware/auth-basic-hash", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||||
|
request->send(200, "text/plain", "Hello, world!");
|
||||||
|
})
|
||||||
|
.addMiddleware(&basicAuthHash);
|
||||||
|
|
||||||
|
// digest authentication
|
||||||
|
// curl -v -X GET -H "origin: http://192.168.4.1" -u admin:admin --digest http://192.168.4.1/middleware/auth-digest
|
||||||
|
server.on("/middleware/auth-digest", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||||
|
request->send(200, "text/plain", "Hello, world!");
|
||||||
|
})
|
||||||
|
.addMiddleware(&digestAuth);
|
||||||
|
|
||||||
|
// digest authentication with hash
|
||||||
|
// curl -v -X GET -H "origin: http://192.168.4.1" -u admin:admin --digest http://192.168.4.1/middleware/auth-digest-hash
|
||||||
|
server.on("/middleware/auth-digest-hash", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||||
|
request->send(200, "text/plain", "Hello, world!");
|
||||||
|
})
|
||||||
|
.addMiddleware(&digestAuthHash);
|
||||||
|
|
||||||
|
// test digest auth with cors
|
||||||
|
// curl -v -X GET -H "origin: http://192.168.4.1" --digest -u user:password http://192.168.4.1/middleware/auth-custom
|
||||||
|
server.on("/middleware/auth-custom", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||||
|
String buffer = "Hello ";
|
||||||
|
buffer.concat(request->getAttribute("user"));
|
||||||
|
buffer.concat(" with role: ");
|
||||||
|
buffer.concat(request->getAttribute("role"));
|
||||||
|
request->send(200, "text/plain", buffer);
|
||||||
|
})
|
||||||
|
.addMiddlewares({&complexAuth, &authz});
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// curl -v -X GET -H "origin: http://192.168.4.1" http://192.168.4.1/redirect
|
||||||
|
// curl -v -X POST -H "origin: http://192.168.4.1" http://192.168.4.1/redirect
|
||||||
|
server.on("/redirect", HTTP_GET | HTTP_POST, [](AsyncWebServerRequest* request) {
|
||||||
|
request->redirect("/");
|
||||||
|
});
|
||||||
|
|
||||||
|
server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||||
|
request->send(200, "text/plain", "Hello, world");
|
||||||
|
});
|
||||||
|
|
||||||
|
server.on("/file", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||||
|
request->send(LittleFS, "/index.html");
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
❯ curl -I -X HEAD http://192.168.4.1/download
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Content-Length: 1024
|
||||||
|
Content-Type: application/octet-stream
|
||||||
|
Connection: close
|
||||||
|
Accept-Ranges: bytes
|
||||||
|
*/
|
||||||
|
// Ref: https://github.com/mathieucarbou/ESPAsyncWebServer/pull/80
|
||||||
|
server.on("/download", HTTP_HEAD | HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||||
|
if (request->method() == HTTP_HEAD) {
|
||||||
|
AsyncWebServerResponse* response = request->beginResponse(200, "application/octet-stream");
|
||||||
|
response->addHeader(asyncsrv::T_Accept_Ranges, "bytes");
|
||||||
|
response->addHeader(asyncsrv::T_Content_Length, 10);
|
||||||
|
response->setContentLength(1024); // overrides previous one
|
||||||
|
response->addHeader(asyncsrv::T_Content_Type, "foo");
|
||||||
|
response->setContentType("application/octet-stream"); // overrides previous one
|
||||||
|
// ...
|
||||||
|
request->send(response);
|
||||||
|
} else {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Send a GET request to <IP>/get?message=<message>
|
||||||
|
server.on("/get", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||||
|
String message;
|
||||||
|
if (request->hasParam(PARAM_MESSAGE)) {
|
||||||
|
message = request->getParam(PARAM_MESSAGE)->value();
|
||||||
|
} else {
|
||||||
|
message = "No message sent";
|
||||||
|
}
|
||||||
|
request->send(200, "text/plain", "Hello, GET: " + message);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Send a POST request to <IP>/post with a form field message set to <message>
|
||||||
|
server.on("/post", HTTP_POST, [](AsyncWebServerRequest* request) {
|
||||||
|
String message;
|
||||||
|
if (request->hasParam(PARAM_MESSAGE, true)) {
|
||||||
|
message = request->getParam(PARAM_MESSAGE, true)->value();
|
||||||
|
} else {
|
||||||
|
message = "No message sent";
|
||||||
|
}
|
||||||
|
request->send(200, "text/plain", "Hello, POST: " + message);
|
||||||
|
});
|
||||||
|
|
||||||
|
#if ASYNC_JSON_SUPPORT == 1
|
||||||
|
// JSON
|
||||||
|
|
||||||
|
// receives JSON and sends JSON
|
||||||
|
jsonHandler->onRequest([](AsyncWebServerRequest* request, JsonVariant& json) {
|
||||||
|
// JsonObject jsonObj = json.as<JsonObject>();
|
||||||
|
// ...
|
||||||
|
|
||||||
|
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||||
|
JsonObject root = response->getRoot().to<JsonObject>();
|
||||||
|
root["hello"] = "world";
|
||||||
|
response->setLength();
|
||||||
|
request->send(response);
|
||||||
|
});
|
||||||
|
|
||||||
|
// sends JSON
|
||||||
|
server.on("/json1", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||||
|
AsyncJsonResponse* response = new AsyncJsonResponse();
|
||||||
|
JsonObject root = response->getRoot().to<JsonObject>();
|
||||||
|
root["hello"] = "world";
|
||||||
|
response->setLength();
|
||||||
|
request->send(response);
|
||||||
|
});
|
||||||
|
|
||||||
|
// MessagePack
|
||||||
|
|
||||||
|
// receives MessagePack and sends MessagePack
|
||||||
|
msgPackHandler->onRequest([](AsyncWebServerRequest* request, JsonVariant& json) {
|
||||||
|
// JsonObject jsonObj = json.as<JsonObject>();
|
||||||
|
// ...
|
||||||
|
|
||||||
|
AsyncMessagePackResponse* response = new AsyncMessagePackResponse();
|
||||||
|
JsonObject root = response->getRoot().to<JsonObject>();
|
||||||
|
root["hello"] = "world";
|
||||||
|
response->setLength();
|
||||||
|
request->send(response);
|
||||||
|
});
|
||||||
|
|
||||||
|
// sends MessagePack
|
||||||
|
server.on("/msgpack1", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||||
|
AsyncMessagePackResponse* response = new AsyncMessagePackResponse();
|
||||||
|
JsonObject root = response->getRoot().to<JsonObject>();
|
||||||
|
root["hello"] = "world";
|
||||||
|
response->setLength();
|
||||||
|
request->send(response);
|
||||||
|
});
|
||||||
|
#endif
|
||||||
|
|
||||||
|
events.onConnect([](AsyncEventSourceClient* client) {
|
||||||
|
if (client->lastId()) {
|
||||||
|
Serial.printf("SSE Client reconnected! Last message ID that it gat is: %" PRIu32 "\n", client->lastId());
|
||||||
|
}
|
||||||
|
client->send("hello!", NULL, millis(), 1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
server.on("/sse", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||||
|
request->send(200, "text/html", SSE_HTLM);
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.onEvent([](AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len) {
|
||||||
|
(void)len;
|
||||||
|
if (type == WS_EVT_CONNECT) {
|
||||||
|
Serial.println("ws connect");
|
||||||
|
client->setCloseClientOnQueueFull(false);
|
||||||
|
client->ping();
|
||||||
|
} else if (type == WS_EVT_DISCONNECT) {
|
||||||
|
Serial.println("ws disconnect");
|
||||||
|
} else if (type == WS_EVT_ERROR) {
|
||||||
|
Serial.println("ws error");
|
||||||
|
} else if (type == WS_EVT_PONG) {
|
||||||
|
Serial.println("ws pong");
|
||||||
|
} else if (type == WS_EVT_DATA) {
|
||||||
|
AwsFrameInfo* info = (AwsFrameInfo*)arg;
|
||||||
|
String msg = "";
|
||||||
|
if (info->final && info->index == 0 && info->len == len) {
|
||||||
|
if (info->opcode == WS_TEXT) {
|
||||||
|
data[len] = 0;
|
||||||
|
Serial.printf("ws text: %s\n", (char*)data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.addHandler(&events);
|
||||||
|
server.addHandler(&ws);
|
||||||
|
|
||||||
|
#if ASYNC_JSON_SUPPORT == 1
|
||||||
|
server.addHandler(jsonHandler);
|
||||||
|
server.addHandler(msgPackHandler);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
server.onNotFound(notFound);
|
||||||
|
|
||||||
|
server.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t lastSSE = 0;
|
||||||
|
uint32_t deltaSSE = 5;
|
||||||
|
|
||||||
|
uint32_t lastWS = 0;
|
||||||
|
uint32_t deltaWS = 100;
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
uint32_t now = millis();
|
||||||
|
if (now - lastSSE >= deltaSSE) {
|
||||||
|
events.send(String("ping-") + now, "heartbeat", now);
|
||||||
|
lastSSE = millis();
|
||||||
|
}
|
||||||
|
if (now - lastWS >= deltaWS) {
|
||||||
|
ws.printfAll("kp%.4f", (10.0 / 3.0));
|
||||||
|
// ws.getClients
|
||||||
|
for (auto& client : ws.getClients()) {
|
||||||
|
client.text("kp%.4f", (10.0 / 3.0));
|
||||||
|
}
|
||||||
|
lastWS = millis();
|
||||||
|
}
|
||||||
|
}
|
37
examples/StreamFiles/StreamConcat.h
Normal file
37
examples/StreamFiles/StreamConcat.h
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Stream.h>
|
||||||
|
|
||||||
|
class StreamConcat : public Stream {
|
||||||
|
public:
|
||||||
|
StreamConcat(Stream* s1, Stream* s2) : _s1(s1), _s2(s2) {}
|
||||||
|
|
||||||
|
size_t write(__unused const uint8_t* p, __unused size_t n) override { return 0; }
|
||||||
|
size_t write(__unused uint8_t c) override { return 0; }
|
||||||
|
void flush() override {}
|
||||||
|
|
||||||
|
int available() override { return _s1->available() + _s2->available(); }
|
||||||
|
|
||||||
|
int read() override {
|
||||||
|
int c = _s1->read();
|
||||||
|
return c != -1 ? c : _s2->read();
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(TARGET_RP2040)
|
||||||
|
size_t readBytes(char* buffer, size_t length) {
|
||||||
|
#else
|
||||||
|
size_t readBytes(char* buffer, size_t length) override {
|
||||||
|
#endif
|
||||||
|
size_t count = _s1->readBytes(buffer, length);
|
||||||
|
return count > 0 ? count : _s2->readBytes(buffer, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
int peek() override {
|
||||||
|
int c = _s1->peek();
|
||||||
|
return c != -1 ? c : _s2->peek();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Stream* _s1;
|
||||||
|
Stream* _s2;
|
||||||
|
};
|
87
examples/StreamFiles/StreamFiles.ino
Normal file
87
examples/StreamFiles/StreamFiles.ino
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
#include <Arduino.h>
|
||||||
|
#include <DNSServer.h>
|
||||||
|
#ifdef ESP32
|
||||||
|
#include <AsyncTCP.h>
|
||||||
|
#include <WiFi.h>
|
||||||
|
#elif defined(ESP8266)
|
||||||
|
#include <ESP8266WiFi.h>
|
||||||
|
#include <ESPAsyncTCP.h>
|
||||||
|
#elif defined(TARGET_RP2040)
|
||||||
|
#include <WebServer.h>
|
||||||
|
#include <WiFi.h>
|
||||||
|
#endif
|
||||||
|
#include "StreamConcat.h"
|
||||||
|
#include "StreamString.h"
|
||||||
|
#include <ESPAsyncWebServer.h>
|
||||||
|
#include <LittleFS.h>
|
||||||
|
|
||||||
|
DNSServer dnsServer;
|
||||||
|
AsyncWebServer server(80);
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(115200);
|
||||||
|
|
||||||
|
LittleFS.begin();
|
||||||
|
|
||||||
|
#ifndef CONFIG_IDF_TARGET_ESP32H2
|
||||||
|
WiFi.mode(WIFI_AP);
|
||||||
|
WiFi.softAP("esp-captive");
|
||||||
|
|
||||||
|
dnsServer.start(53, "*", WiFi.softAPIP());
|
||||||
|
#endif
|
||||||
|
|
||||||
|
File file1 = LittleFS.open("/header.html", "w");
|
||||||
|
file1.print("<html><head><title>ESP Captive Portal</title><meta http-equiv=\"refresh\" content=\"1\"></head><body>");
|
||||||
|
file1.close();
|
||||||
|
|
||||||
|
File file2 = LittleFS.open("/body.html", "w");
|
||||||
|
file2.print("<h1>Welcome to ESP Captive Portal</h1>");
|
||||||
|
file2.close();
|
||||||
|
|
||||||
|
File file3 = LittleFS.open("/footer.html", "w");
|
||||||
|
file3.print("</body></html>");
|
||||||
|
file3.close();
|
||||||
|
|
||||||
|
server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||||
|
File header = LittleFS.open("/header.html", "r");
|
||||||
|
File body = LittleFS.open("/body.html", "r");
|
||||||
|
StreamConcat stream1(&header, &body);
|
||||||
|
|
||||||
|
StreamString content;
|
||||||
|
#if defined(TARGET_RP2040)
|
||||||
|
content.printf("FreeHeap: %d", rp2040.getFreeHeap());
|
||||||
|
#else
|
||||||
|
content.printf("FreeHeap: %" PRIu32, ESP.getFreeHeap());
|
||||||
|
#endif
|
||||||
|
StreamConcat stream2 = StreamConcat(&stream1, &content);
|
||||||
|
|
||||||
|
File footer = LittleFS.open("/footer.html", "r");
|
||||||
|
StreamConcat stream3 = StreamConcat(&stream2, &footer);
|
||||||
|
|
||||||
|
request->send(stream3, "text/html", stream3.available());
|
||||||
|
header.close();
|
||||||
|
body.close();
|
||||||
|
footer.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
server.onNotFound([](AsyncWebServerRequest* request) {
|
||||||
|
request->send(404, "text/plain", "Not found");
|
||||||
|
});
|
||||||
|
|
||||||
|
server.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t last = 0;
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
// dnsServer.processNextRequest();
|
||||||
|
|
||||||
|
if (millis() - last > 2000) {
|
||||||
|
#if defined(TARGET_RP2040)
|
||||||
|
Serial.printf("FreeHeap: %d", rp2040.getFreeHeap());
|
||||||
|
#else
|
||||||
|
Serial.printf("FreeHeap: %" PRIu32, ESP.getFreeHeap());
|
||||||
|
#endif
|
||||||
|
last = millis();
|
||||||
|
}
|
||||||
|
}
|
40
examples/StreamFiles/StreamString.h
Normal file
40
examples/StreamFiles/StreamString.h
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Stream.h>
|
||||||
|
|
||||||
|
class StreamString : public Stream {
|
||||||
|
public:
|
||||||
|
size_t write(const uint8_t* p, size_t n) override { return _buffer.concat(reinterpret_cast<const char*>(p), n) ? n : 0; }
|
||||||
|
size_t write(uint8_t c) override { return _buffer.concat(static_cast<char>(c)) ? 1 : 0; }
|
||||||
|
void flush() override {}
|
||||||
|
|
||||||
|
int available() override { return static_cast<int>(_buffer.length()); }
|
||||||
|
|
||||||
|
int read() override {
|
||||||
|
if (_buffer.length() == 0)
|
||||||
|
return -1;
|
||||||
|
char c = _buffer[0];
|
||||||
|
_buffer.remove(0, 1);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(TARGET_RP2040)
|
||||||
|
size_t readBytes(char* buffer, size_t length) {
|
||||||
|
#else
|
||||||
|
size_t readBytes(char* buffer, size_t length) override {
|
||||||
|
#endif
|
||||||
|
if (length > _buffer.length())
|
||||||
|
length = _buffer.length();
|
||||||
|
// Don't use _str.ToCharArray() because it inserts a terminator
|
||||||
|
memcpy(buffer, _buffer.c_str(), length);
|
||||||
|
_buffer.remove(0, static_cast<unsigned int>(length));
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
int peek() override { return _buffer.length() > 0 ? _buffer[0] : -1; }
|
||||||
|
|
||||||
|
const String& buffer() const { return _buffer; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
String _buffer;
|
||||||
|
};
|
107
examples/issues/Issue14/Issue14.ino
Normal file
107
examples/issues/Issue14/Issue14.ino
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
#include <DNSServer.h>
|
||||||
|
#ifdef ESP32
|
||||||
|
#include <AsyncTCP.h>
|
||||||
|
#include <WiFi.h>
|
||||||
|
#elif defined(ESP8266)
|
||||||
|
#include <ESP8266WiFi.h>
|
||||||
|
#include <ESPAsyncTCP.h>
|
||||||
|
#elif defined(TARGET_RP2040)
|
||||||
|
#include <WebServer.h>
|
||||||
|
#include <WiFi.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "ESPAsyncWebServer.h"
|
||||||
|
|
||||||
|
const char appWebPage[] PROGMEM = R"rawliteral(
|
||||||
|
<body>
|
||||||
|
<button id="button1" onclick="fetch('/button1');">Relay1</button>
|
||||||
|
<script>
|
||||||
|
const evtSource = new EventSource("/events");
|
||||||
|
button1 = document.getElementById("button1");
|
||||||
|
evtSource.addEventListener('state', (e) => {
|
||||||
|
const data = JSON.parse(e.data);
|
||||||
|
console.log('Event Source data: ', data);
|
||||||
|
if (data.button1) {
|
||||||
|
button1.style.backgroundColor = "green";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
button1.style.backgroundColor = "red";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
)rawliteral";
|
||||||
|
|
||||||
|
AsyncWebServer server(80);
|
||||||
|
AsyncEventSource events("/events");
|
||||||
|
|
||||||
|
const uint32_t interval = 1000;
|
||||||
|
const int button1Pin = 4;
|
||||||
|
|
||||||
|
uint32_t lastSend = 0;
|
||||||
|
|
||||||
|
void prepareJson(String& buffer) {
|
||||||
|
buffer.reserve(512);
|
||||||
|
buffer.concat("{\"button1\":");
|
||||||
|
buffer.concat(digitalRead(button1Pin) == LOW);
|
||||||
|
buffer.concat(",\"1234567890abcdefghij1234567890abcdefghij1234567890abcdefghij1234567890abcdefghij1234567890abcdefghij1234567890abcdefghij\":");
|
||||||
|
buffer.concat(random(0, 999999999));
|
||||||
|
buffer.concat("}");
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(115200);
|
||||||
|
#if ARDUINO_USB_CDC_ON_BOOT
|
||||||
|
Serial.setTxTimeoutMs(0);
|
||||||
|
delay(100);
|
||||||
|
#else
|
||||||
|
while (!Serial)
|
||||||
|
yield();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
randomSeed(micros());
|
||||||
|
|
||||||
|
pinMode(button1Pin, OUTPUT);
|
||||||
|
digitalWrite(button1Pin, HIGH);
|
||||||
|
|
||||||
|
WiFi.softAP("esp-captive");
|
||||||
|
|
||||||
|
server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||||
|
request->send(200, "text/html", appWebPage);
|
||||||
|
});
|
||||||
|
|
||||||
|
server.on("/button1", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||||
|
request->send(200, "text/plain", "OK");
|
||||||
|
digitalWrite(button1Pin, digitalRead(button1Pin) == LOW ? HIGH : LOW);
|
||||||
|
|
||||||
|
String buffer;
|
||||||
|
prepareJson(buffer);
|
||||||
|
ESP_LOGI("async_tcp", "Sending from handler...");
|
||||||
|
events.send(buffer.c_str(), "state", millis());
|
||||||
|
ESP_LOGI("async_tcp", "Sent from handler!");
|
||||||
|
});
|
||||||
|
|
||||||
|
events.onConnect([](AsyncEventSourceClient* client) {
|
||||||
|
String buffer;
|
||||||
|
prepareJson(buffer);
|
||||||
|
ESP_LOGI("async_tcp", "Sending from onConnect...");
|
||||||
|
client->send(buffer.c_str(), "state", millis(), 5000);
|
||||||
|
ESP_LOGI("async_tcp", "Sent from onConnect!");
|
||||||
|
});
|
||||||
|
|
||||||
|
server.addHandler(&events);
|
||||||
|
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*");
|
||||||
|
|
||||||
|
server.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
if (millis() - lastSend >= interval) {
|
||||||
|
String buffer;
|
||||||
|
prepareJson(buffer);
|
||||||
|
ESP_LOGI("loop", "Sending...");
|
||||||
|
events.send(buffer.c_str(), "state", millis());
|
||||||
|
ESP_LOGI("loop", "Sent!");
|
||||||
|
lastSend = millis();
|
||||||
|
}
|
||||||
|
}
|
@ -1 +0,0 @@
|
|||||||
-DASYNCWEBSERVER_REGEX=1
|
|
@ -1,77 +0,0 @@
|
|||||||
//
|
|
||||||
// A simple server implementation with regex routes:
|
|
||||||
// * serve static messages
|
|
||||||
// * read GET and POST parameters
|
|
||||||
// * handle missing pages / 404s
|
|
||||||
//
|
|
||||||
|
|
||||||
// Add buildflag ASYNCWEBSERVER_REGEX to enable the regex support
|
|
||||||
|
|
||||||
// For platformio: platformio.ini:
|
|
||||||
// build_flags =
|
|
||||||
// -DASYNCWEBSERVER_REGEX
|
|
||||||
|
|
||||||
// For arduino IDE: create/update platform.local.txt
|
|
||||||
// Windows: C:\Users\(username)\AppData\Local\Arduino15\packages\espxxxx\hardware\espxxxx\{version}\platform.local.txt
|
|
||||||
// Linux: ~/.arduino15/packages/espxxxx/hardware/espxxxx/{version}/platform.local.txt
|
|
||||||
//
|
|
||||||
// compiler.cpp.extra_flags=-DASYNCWEBSERVER_REGEX=1
|
|
||||||
|
|
||||||
#include <Arduino.h>
|
|
||||||
#ifdef ESP32
|
|
||||||
#include <WiFi.h>
|
|
||||||
#include <AsyncTCP.h>
|
|
||||||
#elif defined(ESP8266)
|
|
||||||
#include <ESP8266WiFi.h>
|
|
||||||
#include <ESPAsyncTCP.h>
|
|
||||||
#endif
|
|
||||||
#include <ESPAsyncWebServer.h>
|
|
||||||
|
|
||||||
AsyncWebServer server(80);
|
|
||||||
|
|
||||||
const char* ssid = "YOUR_SSID";
|
|
||||||
const char* password = "YOUR_PASSWORD";
|
|
||||||
|
|
||||||
const char* PARAM_MESSAGE = "message";
|
|
||||||
|
|
||||||
void notFound(AsyncWebServerRequest *request) {
|
|
||||||
request->send(404, "text/plain", "Not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
void setup() {
|
|
||||||
|
|
||||||
Serial.begin(115200);
|
|
||||||
WiFi.mode(WIFI_STA);
|
|
||||||
WiFi.begin(ssid, password);
|
|
||||||
if (WiFi.waitForConnectResult() != WL_CONNECTED) {
|
|
||||||
Serial.printf("WiFi Failed!\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Serial.print("IP Address: ");
|
|
||||||
Serial.println(WiFi.localIP());
|
|
||||||
|
|
||||||
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
|
|
||||||
request->send(200, "text/plain", "Hello, world");
|
|
||||||
});
|
|
||||||
|
|
||||||
// Send a GET request to <IP>/sensor/<number>
|
|
||||||
server.on("^\\/sensor\\/([0-9]+)$", HTTP_GET, [] (AsyncWebServerRequest *request) {
|
|
||||||
String sensorNumber = request->pathArg(0);
|
|
||||||
request->send(200, "text/plain", "Hello, sensor: " + sensorNumber);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Send a GET request to <IP>/sensor/<number>/action/<action>
|
|
||||||
server.on("^\\/sensor\\/([0-9]+)\\/action\\/([a-zA-Z0-9]+)$", HTTP_GET, [] (AsyncWebServerRequest *request) {
|
|
||||||
String sensorNumber = request->pathArg(0);
|
|
||||||
String action = request->pathArg(1);
|
|
||||||
request->send(200, "text/plain", "Hello, sensor: " + sensorNumber + ", with action: " + action);
|
|
||||||
});
|
|
||||||
|
|
||||||
server.onNotFound(notFound);
|
|
||||||
|
|
||||||
server.begin();
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop() {
|
|
||||||
}
|
|
@ -1,74 +0,0 @@
|
|||||||
//
|
|
||||||
// A simple server implementation showing how to:
|
|
||||||
// * serve static messages
|
|
||||||
// * read GET and POST parameters
|
|
||||||
// * handle missing pages / 404s
|
|
||||||
//
|
|
||||||
|
|
||||||
#include <Arduino.h>
|
|
||||||
#ifdef ESP32
|
|
||||||
#include <WiFi.h>
|
|
||||||
#include <AsyncTCP.h>
|
|
||||||
#elif defined(ESP8266)
|
|
||||||
#include <ESP8266WiFi.h>
|
|
||||||
#include <ESPAsyncTCP.h>
|
|
||||||
#endif
|
|
||||||
#include <ESPAsyncWebServer.h>
|
|
||||||
|
|
||||||
AsyncWebServer server(80);
|
|
||||||
|
|
||||||
const char* ssid = "YOUR_SSID";
|
|
||||||
const char* password = "YOUR_PASSWORD";
|
|
||||||
|
|
||||||
const char* PARAM_MESSAGE = "message";
|
|
||||||
|
|
||||||
void notFound(AsyncWebServerRequest *request) {
|
|
||||||
request->send(404, "text/plain", "Not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
void setup() {
|
|
||||||
|
|
||||||
Serial.begin(115200);
|
|
||||||
WiFi.mode(WIFI_STA);
|
|
||||||
WiFi.begin(ssid, password);
|
|
||||||
if (WiFi.waitForConnectResult() != WL_CONNECTED) {
|
|
||||||
Serial.printf("WiFi Failed!\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Serial.print("IP Address: ");
|
|
||||||
Serial.println(WiFi.localIP());
|
|
||||||
|
|
||||||
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
|
|
||||||
request->send(200, "text/plain", "Hello, world");
|
|
||||||
});
|
|
||||||
|
|
||||||
// Send a GET request to <IP>/get?message=<message>
|
|
||||||
server.on("/get", HTTP_GET, [] (AsyncWebServerRequest *request) {
|
|
||||||
String message;
|
|
||||||
if (request->hasParam(PARAM_MESSAGE)) {
|
|
||||||
message = request->getParam(PARAM_MESSAGE)->value();
|
|
||||||
} else {
|
|
||||||
message = "No message sent";
|
|
||||||
}
|
|
||||||
request->send(200, "text/plain", "Hello, GET: " + message);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Send a POST request to <IP>/post with a form field message set to <message>
|
|
||||||
server.on("/post", HTTP_POST, [](AsyncWebServerRequest *request){
|
|
||||||
String message;
|
|
||||||
if (request->hasParam(PARAM_MESSAGE, true)) {
|
|
||||||
message = request->getParam(PARAM_MESSAGE, true)->value();
|
|
||||||
} else {
|
|
||||||
message = "No message sent";
|
|
||||||
}
|
|
||||||
request->send(200, "text/plain", "Hello, POST: " + message);
|
|
||||||
});
|
|
||||||
|
|
||||||
server.onNotFound(notFound);
|
|
||||||
|
|
||||||
server.begin();
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop() {
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
JsonArray KEYWORD1
|
|
||||||
add KEYWORD2
|
|
||||||
createArray KEYWORD3
|
|
19
library.json
19
library.json
@ -1,19 +0,0 @@
|
|||||||
{
|
|
||||||
"name":"ESPAsyncWebServer-esphome",
|
|
||||||
"description":"Asynchronous HTTP and WebSocket Server Library for ESP8266 and ESP32",
|
|
||||||
"keywords":"http,async,websocket,webserver",
|
|
||||||
"authors":
|
|
||||||
{
|
|
||||||
"name": "ESPHome Team",
|
|
||||||
"maintainer": true
|
|
||||||
},
|
|
||||||
"repository":
|
|
||||||
{
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/esphome/ESPAsyncWebServer.git"
|
|
||||||
},
|
|
||||||
"version": "3.1.0",
|
|
||||||
"license": "LGPL-3.0",
|
|
||||||
"frameworks": "arduino",
|
|
||||||
"platforms": ["espressif8266", "espressif32", "libretiny"]
|
|
||||||
}
|
|
64
library.json_
Normal file
64
library.json_
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
{
|
||||||
|
"name": "ESPAsyncWebServer",
|
||||||
|
"version": "3.3.7",
|
||||||
|
"description": "Asynchronous HTTP and WebSocket Server Library for ESP32, ESP8266 and RP2040. Supports: WebSocket, SSE, Authentication, Arduino Json 7, File Upload, Static File serving, URL Rewrite, URL Redirect, etc.",
|
||||||
|
"keywords": "http,async,websocket,webserver",
|
||||||
|
"homepage": "https://github.com/mathieucarbou/ESPAsyncWebServer",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/mathieucarbou/ESPAsyncWebServer.git"
|
||||||
|
},
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Hristo Gochkov"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Mathieu Carbou",
|
||||||
|
"maintainer": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "LGPL-3.0",
|
||||||
|
"frameworks": "arduino",
|
||||||
|
"platforms": [
|
||||||
|
"espressif32",
|
||||||
|
"espressif8266",
|
||||||
|
"raspberrypi"
|
||||||
|
],
|
||||||
|
"dependencies": [
|
||||||
|
{
|
||||||
|
"owner": "mathieucarbou",
|
||||||
|
"name": "AsyncTCP",
|
||||||
|
"version": "^3.2.5",
|
||||||
|
"platforms": "espressif32"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"owner": "esphome",
|
||||||
|
"name": "ESPAsyncTCP-esphome",
|
||||||
|
"version": "^2.0.0",
|
||||||
|
"platforms": "espressif8266"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Hash",
|
||||||
|
"platforms": "espressif8266"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"owner": "khoih-prog",
|
||||||
|
"name": "AsyncTCP_RP2040W",
|
||||||
|
"version": "^1.2.0",
|
||||||
|
"platforms": "raspberrypi"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"export": {
|
||||||
|
"include": [
|
||||||
|
"examples",
|
||||||
|
"src",
|
||||||
|
"library.json",
|
||||||
|
"library.properties",
|
||||||
|
"LICENSE",
|
||||||
|
"README.md"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"build": {
|
||||||
|
"libCompatMode": "strict"
|
||||||
|
}
|
||||||
|
}
|
10
library.properties
Normal file
10
library.properties
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
name=ESPAsyncWebServer
|
||||||
|
version=3.3.7
|
||||||
|
author=Me-No-Dev
|
||||||
|
maintainer=Mathieu Carbou <mathieu.carbou@gmail.com>
|
||||||
|
sentence=Asynchronous HTTP and WebSocket Server Library for ESP32, ESP8266 and RP2040
|
||||||
|
paragraph=Supports: WebSocket, SSE, Authentication, Arduino Json 7, File Upload, Static File serving, URL Rewrite, URL Redirect, etc
|
||||||
|
category=Other
|
||||||
|
url=https://github.com/mathieucarbou/ESPAsyncWebServer
|
||||||
|
architectures=*
|
||||||
|
license=LGPL-3.0
|
111
platformio.ini
Normal file
111
platformio.ini
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
[platformio]
|
||||||
|
default_envs = arduino-2, arduino-3, arduino-310rc1, esp8266, raspberrypi
|
||||||
|
lib_dir = .
|
||||||
|
; src_dir = examples/CaptivePortal
|
||||||
|
src_dir = examples/SimpleServer
|
||||||
|
; src_dir = examples/StreamFiles
|
||||||
|
; src_dir = examples/Filters
|
||||||
|
; src_dir = examples/Draft
|
||||||
|
; src_dir = examples/issues/Issue14
|
||||||
|
|
||||||
|
[env]
|
||||||
|
framework = arduino
|
||||||
|
build_flags =
|
||||||
|
-Wall -Wextra
|
||||||
|
-Wno-unused-parameter
|
||||||
|
-D CONFIG_ARDUHAL_LOG_COLORS
|
||||||
|
-D CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_VERBOSE
|
||||||
|
-D CONFIG_ASYNC_TCP_MAX_ACK_TIME=3000
|
||||||
|
-D CONFIG_ASYNC_TCP_PRIORITY=10
|
||||||
|
-D CONFIG_ASYNC_TCP_QUEUE_SIZE=128
|
||||||
|
-D CONFIG_ASYNC_TCP_RUNNING_CORE=1
|
||||||
|
-D CONFIG_ASYNC_TCP_STACK_SIZE=4096
|
||||||
|
upload_protocol = esptool
|
||||||
|
monitor_speed = 115200
|
||||||
|
monitor_filters = esp32_exception_decoder, log2file
|
||||||
|
lib_deps =
|
||||||
|
; bblanchon/ArduinoJson @ 5.13.4
|
||||||
|
; bblanchon/ArduinoJson @ 6.21.5
|
||||||
|
bblanchon/ArduinoJson @ 7.2.0
|
||||||
|
mathieucarbou/AsyncTCP @ 3.2.5
|
||||||
|
board = esp32dev
|
||||||
|
|
||||||
|
[env:arduino-2]
|
||||||
|
platform = espressif32@6.9.0
|
||||||
|
|
||||||
|
[env:arduino-3]
|
||||||
|
platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip
|
||||||
|
; board = esp32-s3-devkitc-1
|
||||||
|
; board = esp32-c6-devkitc-1
|
||||||
|
|
||||||
|
[env:arduino-3-no-json]
|
||||||
|
platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip
|
||||||
|
; board = esp32-s3-devkitc-1
|
||||||
|
; board = esp32-c6-devkitc-1
|
||||||
|
lib_deps =
|
||||||
|
mathieucarbou/AsyncTCP @ 3.2.5
|
||||||
|
|
||||||
|
[env:arduino-310rc1]
|
||||||
|
platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.10-rc1/platform-espressif32.zip
|
||||||
|
; board = esp32-s3-devkitc-1
|
||||||
|
; board = esp32-c6-devkitc-1
|
||||||
|
|
||||||
|
[env:esp8266]
|
||||||
|
platform = espressif8266
|
||||||
|
board = huzzah
|
||||||
|
; board = d1_mini
|
||||||
|
lib_deps =
|
||||||
|
bblanchon/ArduinoJson @ 7.2.0
|
||||||
|
esphome/ESPAsyncTCP-esphome @ 2.0.0
|
||||||
|
|
||||||
|
; PlatformIO support for Raspberry Pi Pico is not official
|
||||||
|
; https://github.com/platformio/platform-raspberrypi/pull/36
|
||||||
|
; https://github.com/earlephilhower/arduino-pico/blob/master/docs/platformio.rst
|
||||||
|
; board settings: https://github.com/earlephilhower/arduino-pico/blob/master/tools/json/rpipico.json
|
||||||
|
[env:raspberrypi]
|
||||||
|
upload_protocol = picotool
|
||||||
|
; platform = raspberrypi
|
||||||
|
platform = https://github.com/maxgerhardt/platform-raspberrypi.git#f2687073f73d554c9db41f29b4769fd9703f4e55
|
||||||
|
board = rpipicow
|
||||||
|
lib_deps =
|
||||||
|
bblanchon/ArduinoJson @ 7.2.0
|
||||||
|
khoih-prog/AsyncTCP_RP2040W @ 1.2.0
|
||||||
|
build_flags = ${env.build_flags}
|
||||||
|
-Wno-missing-field-initializers
|
||||||
|
|
||||||
|
; CI
|
||||||
|
|
||||||
|
[env:ci-arduino-2]
|
||||||
|
platform = espressif32@6.9.0
|
||||||
|
board = ${sysenv.PIO_BOARD}
|
||||||
|
|
||||||
|
[env:ci-arduino-3]
|
||||||
|
platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip
|
||||||
|
board = ${sysenv.PIO_BOARD}
|
||||||
|
|
||||||
|
[env:ci-arduino-3-no-json]
|
||||||
|
platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip
|
||||||
|
board = ${sysenv.PIO_BOARD}
|
||||||
|
lib_deps =
|
||||||
|
mathieucarbou/AsyncTCP @ 3.2.5
|
||||||
|
|
||||||
|
[env:ci-arduino-310rc1]
|
||||||
|
platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.10-rc1/platform-espressif32.zip
|
||||||
|
board = ${sysenv.PIO_BOARD}
|
||||||
|
|
||||||
|
[env:ci-esp8266]
|
||||||
|
platform = espressif8266
|
||||||
|
board = ${sysenv.PIO_BOARD}
|
||||||
|
lib_deps =
|
||||||
|
bblanchon/ArduinoJson @ 7.2.0
|
||||||
|
esphome/ESPAsyncTCP-esphome @ 2.0.0
|
||||||
|
|
||||||
|
[env:ci-raspberrypi]
|
||||||
|
; platform = raspberrypi
|
||||||
|
platform = https://github.com/maxgerhardt/platform-raspberrypi.git#f2687073f73d554c9db41f29b4769fd9703f4e55
|
||||||
|
board = ${sysenv.PIO_BOARD}
|
||||||
|
lib_deps =
|
||||||
|
bblanchon/ArduinoJson @ 7.2.0
|
||||||
|
khoih-prog/AsyncTCP_RP2040W @ 1.2.0
|
||||||
|
build_flags = ${env.build_flags}
|
||||||
|
-Wno-missing-field-initializers
|
@ -18,65 +18,70 @@
|
|||||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
*/
|
*/
|
||||||
#include "Arduino.h"
|
#include "Arduino.h"
|
||||||
|
#if defined(ESP32)
|
||||||
|
#include <rom/ets_sys.h>
|
||||||
|
#endif
|
||||||
#include "AsyncEventSource.h"
|
#include "AsyncEventSource.h"
|
||||||
|
|
||||||
static String generateEventMessage(const char *message, const char *event, uint32_t id, uint32_t reconnect){
|
using namespace asyncsrv;
|
||||||
String ev = "";
|
|
||||||
|
|
||||||
if(reconnect){
|
static String generateEventMessage(const char* message, const char* event, uint32_t id, uint32_t reconnect) {
|
||||||
ev += "retry: ";
|
String ev;
|
||||||
ev += String(reconnect);
|
|
||||||
ev += "\r\n";
|
if (reconnect) {
|
||||||
|
ev += T_retry_;
|
||||||
|
ev += reconnect;
|
||||||
|
ev += T_rn;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(id){
|
if (id) {
|
||||||
ev += "id: ";
|
ev += T_id__;
|
||||||
ev += String(id);
|
ev += id;
|
||||||
ev += "\r\n";
|
ev += T_rn;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(event != NULL){
|
if (event != NULL) {
|
||||||
ev += "event: ";
|
ev += T_event_;
|
||||||
ev += String(event);
|
ev += event;
|
||||||
ev += "\r\n";
|
ev += T_rn;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(message != NULL){
|
if (message != NULL) {
|
||||||
size_t messageLen = strlen(message);
|
size_t messageLen = strlen(message);
|
||||||
char * lineStart = (char *)message;
|
char* lineStart = (char*)message;
|
||||||
char * lineEnd;
|
char* lineEnd;
|
||||||
do {
|
do {
|
||||||
char * nextN = strchr(lineStart, '\n');
|
char* nextN = strchr(lineStart, '\n');
|
||||||
char * nextR = strchr(lineStart, '\r');
|
char* nextR = strchr(lineStart, '\r');
|
||||||
if(nextN == NULL && nextR == NULL){
|
if (nextN == NULL && nextR == NULL) {
|
||||||
size_t llen = ((char *)message + messageLen) - lineStart;
|
size_t llen = ((char*)message + messageLen) - lineStart;
|
||||||
char * ldata = (char *)malloc(llen+1);
|
char* ldata = (char*)malloc(llen + 1);
|
||||||
if(ldata != NULL){
|
if (ldata != NULL) {
|
||||||
memcpy(ldata, lineStart, llen);
|
memcpy(ldata, lineStart, llen);
|
||||||
ldata[llen] = 0;
|
ldata[llen] = 0;
|
||||||
ev += "data: ";
|
ev += T_data_;
|
||||||
ev += ldata;
|
ev += ldata;
|
||||||
ev += "\r\n\r\n";
|
ev += T_rnrn;
|
||||||
free(ldata);
|
free(ldata);
|
||||||
}
|
}
|
||||||
lineStart = (char *)message + messageLen;
|
lineStart = (char*)message + messageLen;
|
||||||
} else {
|
} else {
|
||||||
char * nextLine = NULL;
|
char* nextLine = NULL;
|
||||||
if(nextN != NULL && nextR != NULL){
|
if (nextN != NULL && nextR != NULL) {
|
||||||
if(nextR < nextN){
|
if (nextR < nextN) {
|
||||||
lineEnd = nextR;
|
lineEnd = nextR;
|
||||||
if(nextN == (nextR + 1))
|
if (nextN == (nextR + 1))
|
||||||
nextLine = nextN + 1;
|
nextLine = nextN + 1;
|
||||||
else
|
else
|
||||||
nextLine = nextR + 1;
|
nextLine = nextR + 1;
|
||||||
} else {
|
} else {
|
||||||
lineEnd = nextN;
|
lineEnd = nextN;
|
||||||
if(nextR == (nextN + 1))
|
if (nextR == (nextN + 1))
|
||||||
nextLine = nextR + 1;
|
nextLine = nextR + 1;
|
||||||
else
|
else
|
||||||
nextLine = nextN + 1;
|
nextLine = nextN + 1;
|
||||||
}
|
}
|
||||||
} else if(nextN != NULL){
|
} else if (nextN != NULL) {
|
||||||
lineEnd = nextN;
|
lineEnd = nextN;
|
||||||
nextLine = nextN + 1;
|
nextLine = nextN + 1;
|
||||||
} else {
|
} else {
|
||||||
@ -85,20 +90,20 @@ static String generateEventMessage(const char *message, const char *event, uint3
|
|||||||
}
|
}
|
||||||
|
|
||||||
size_t llen = lineEnd - lineStart;
|
size_t llen = lineEnd - lineStart;
|
||||||
char * ldata = (char *)malloc(llen+1);
|
char* ldata = (char*)malloc(llen + 1);
|
||||||
if(ldata != NULL){
|
if (ldata != NULL) {
|
||||||
memcpy(ldata, lineStart, llen);
|
memcpy(ldata, lineStart, llen);
|
||||||
ldata[llen] = 0;
|
ldata[llen] = 0;
|
||||||
ev += "data: ";
|
ev += T_data_;
|
||||||
ev += ldata;
|
ev += ldata;
|
||||||
ev += "\r\n";
|
ev += T_rn;
|
||||||
free(ldata);
|
free(ldata);
|
||||||
}
|
}
|
||||||
lineStart = nextLine;
|
lineStart = nextLine;
|
||||||
if(lineStart == ((char *)message + messageLen))
|
if (lineStart == ((char*)message + messageLen))
|
||||||
ev += "\r\n";
|
ev += T_rn;
|
||||||
}
|
}
|
||||||
} while(lineStart < ((char *)message + messageLen));
|
} while (lineStart < ((char*)message + messageLen));
|
||||||
}
|
}
|
||||||
|
|
||||||
return ev;
|
return ev;
|
||||||
@ -106,11 +111,10 @@ static String generateEventMessage(const char *message, const char *event, uint3
|
|||||||
|
|
||||||
// Message
|
// Message
|
||||||
|
|
||||||
AsyncEventSourceMessage::AsyncEventSourceMessage(const char * data, size_t len)
|
AsyncEventSourceMessage::AsyncEventSourceMessage(const char* data, size_t len)
|
||||||
: _data(nullptr), _len(len), _sent(0), _acked(0)
|
: _data(nullptr), _len(len), _sent(0), _acked(0) {
|
||||||
{
|
_data = (uint8_t*)malloc(_len + 1);
|
||||||
_data = (uint8_t*)malloc(_len+1);
|
if (_data == nullptr) {
|
||||||
if(_data == nullptr){
|
|
||||||
_len = 0;
|
_len = 0;
|
||||||
} else {
|
} else {
|
||||||
memcpy(_data, data, len);
|
memcpy(_data, data, len);
|
||||||
@ -119,251 +123,286 @@ AsyncEventSourceMessage::AsyncEventSourceMessage(const char * data, size_t len)
|
|||||||
}
|
}
|
||||||
|
|
||||||
AsyncEventSourceMessage::~AsyncEventSourceMessage() {
|
AsyncEventSourceMessage::~AsyncEventSourceMessage() {
|
||||||
if(_data != NULL)
|
if (_data != NULL)
|
||||||
free(_data);
|
free(_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t AsyncEventSourceMessage::ack(size_t len, uint32_t time) {
|
size_t AsyncEventSourceMessage::ack(size_t len, __attribute__((unused)) uint32_t time) {
|
||||||
(void)time;
|
|
||||||
// If the whole message is now acked...
|
// If the whole message is now acked...
|
||||||
if(_acked + len > _len){
|
if (_acked + len > _len) {
|
||||||
// Return the number of extra bytes acked (they will be carried on to the next message)
|
// Return the number of extra bytes acked (they will be carried on to the next message)
|
||||||
const size_t extra = _acked + len - _len;
|
const size_t extra = _acked + len - _len;
|
||||||
_acked = _len;
|
_acked = _len;
|
||||||
return extra;
|
return extra;
|
||||||
}
|
}
|
||||||
// Return that no extra bytes left.
|
// Return that no extra bytes left.
|
||||||
_acked += len;
|
_acked += len;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t AsyncEventSourceMessage::send(AsyncClient *client) {
|
size_t AsyncEventSourceMessage::write(AsyncClient* client) {
|
||||||
if (!client->canSend())
|
if (_sent >= _len || !client->canSend()) {
|
||||||
return 0;
|
|
||||||
const size_t len = _len - _sent;
|
|
||||||
if(client->space() < len){
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
size_t sent = client->add((const char *)_data, len);
|
size_t len = min(_len - _sent, client->space());
|
||||||
client->send();
|
size_t sent = client->add((const char*)_data + _sent, len);
|
||||||
_sent += sent;
|
_sent += sent;
|
||||||
return sent;
|
return sent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t AsyncEventSourceMessage::send(AsyncClient* client) {
|
||||||
|
size_t sent = write(client);
|
||||||
|
return sent && client->send() ? sent : 0;
|
||||||
|
}
|
||||||
|
|
||||||
// Client
|
// Client
|
||||||
|
|
||||||
AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server)
|
AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest* request, AsyncEventSource* server) {
|
||||||
: _messageQueue(LinkedList<AsyncEventSourceMessage *>([](AsyncEventSourceMessage *m){ delete m; }))
|
|
||||||
{
|
|
||||||
_client = request->client();
|
_client = request->client();
|
||||||
_server = server;
|
_server = server;
|
||||||
_lastId = 0;
|
_lastId = 0;
|
||||||
if(request->hasHeader("Last-Event-ID"))
|
if (request->hasHeader(T_Last_Event_ID))
|
||||||
_lastId = atoi(request->getHeader("Last-Event-ID")->value().c_str());
|
_lastId = atoi(request->getHeader(T_Last_Event_ID)->value().c_str());
|
||||||
|
|
||||||
_client->setRxTimeout(0);
|
_client->setRxTimeout(0);
|
||||||
_client->onError(NULL, NULL);
|
_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->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->onPoll([](void* r, AsyncClient* c) { (void)c; ((AsyncEventSourceClient*)(r))->_onPoll(); }, this);
|
||||||
_client->onData(NULL, NULL);
|
_client->onData(NULL, NULL);
|
||||||
_client->onTimeout([this](void *r, AsyncClient* c __attribute__((unused)), uint32_t time){ ((AsyncEventSourceClient*)(r))->_onTimeout(time); }, this);
|
_client->onTimeout([this](void* r, AsyncClient* c __attribute__((unused)), uint32_t time) { ((AsyncEventSourceClient*)(r))->_onTimeout(time); }, this);
|
||||||
_client->onDisconnect([this](void *r, AsyncClient* c){ ((AsyncEventSourceClient*)(r))->_onDisconnect(); delete c; }, this);
|
_client->onDisconnect([this](void* r, AsyncClient* c) { ((AsyncEventSourceClient*)(r))->_onDisconnect(); delete c; }, this);
|
||||||
|
|
||||||
_server->_addClient(this);
|
_server->_addClient(this);
|
||||||
delete request;
|
delete request;
|
||||||
|
|
||||||
|
_client->setNoDelay(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncEventSourceClient::~AsyncEventSourceClient(){
|
AsyncEventSourceClient::~AsyncEventSourceClient() {
|
||||||
_messageQueue.free();
|
#ifdef ESP32
|
||||||
|
std::lock_guard<std::mutex> lock(_lockmq);
|
||||||
|
#endif
|
||||||
|
_messageQueue.clear();
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AsyncEventSourceClient::_queueMessage(AsyncEventSourceMessage *dataMessage){
|
void AsyncEventSourceClient::_queueMessage(const char* message, size_t len) {
|
||||||
if(dataMessage == NULL)
|
#ifdef ESP32
|
||||||
return;
|
// length() is not thread-safe, thus acquiring the lock before this call..
|
||||||
if(!connected()){
|
std::lock_guard<std::mutex> lock(_lockmq);
|
||||||
delete dataMessage;
|
#endif
|
||||||
|
|
||||||
|
if (_messageQueue.size() >= SSE_MAX_QUEUED_MESSAGES) {
|
||||||
|
#ifdef ESP8266
|
||||||
|
ets_printf(String(F("ERROR: Too many messages queued\n")).c_str());
|
||||||
|
#elif defined(ESP32)
|
||||||
|
log_e("Too many messages queued: deleting message");
|
||||||
|
#endif
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(_messageQueue.length() >= SSE_MAX_QUEUED_MESSAGES){
|
|
||||||
ets_printf("ERROR: Too many messages queued\n");
|
_messageQueue.emplace_back(message, len);
|
||||||
delete dataMessage;
|
// runqueue trigger when new messages added
|
||||||
} else {
|
if (_client->canSend()) {
|
||||||
_messageQueue.add(dataMessage);
|
|
||||||
}
|
|
||||||
if(_client->canSend())
|
|
||||||
_runQueue();
|
_runQueue();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AsyncEventSourceClient::_onAck(size_t len, uint32_t time){
|
void AsyncEventSourceClient::_onAck(size_t len __attribute__((unused)), uint32_t time __attribute__((unused))) {
|
||||||
while(len && !_messageQueue.isEmpty()){
|
#ifdef ESP32
|
||||||
len = _messageQueue.front()->ack(len, time);
|
// Same here, acquiring the lock early
|
||||||
if(_messageQueue.front()->finished())
|
std::lock_guard<std::mutex> lock(_lockmq);
|
||||||
_messageQueue.remove(_messageQueue.front());
|
#endif
|
||||||
}
|
|
||||||
|
|
||||||
_runQueue();
|
_runQueue();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AsyncEventSourceClient::_onPoll(){
|
void AsyncEventSourceClient::_onPoll() {
|
||||||
if(!_messageQueue.isEmpty()){
|
#ifdef ESP32
|
||||||
|
// Same here, acquiring the lock early
|
||||||
|
std::lock_guard<std::mutex> lock(_lockmq);
|
||||||
|
#endif
|
||||||
|
if (_messageQueue.size()) {
|
||||||
_runQueue();
|
_runQueue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AsyncEventSourceClient::_onTimeout(uint32_t time __attribute__((unused))) {
|
||||||
void AsyncEventSourceClient::_onTimeout(uint32_t time __attribute__((unused))){
|
|
||||||
_client->close(true);
|
_client->close(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AsyncEventSourceClient::_onDisconnect(){
|
void AsyncEventSourceClient::_onDisconnect() {
|
||||||
_client = NULL;
|
_client = NULL;
|
||||||
_server->_handleDisconnect(this);
|
_server->_handleDisconnect(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AsyncEventSourceClient::close(){
|
void AsyncEventSourceClient::close() {
|
||||||
if(_client != NULL)
|
if (_client != NULL)
|
||||||
_client->close();
|
_client->close();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AsyncEventSourceClient::write(const char * message, size_t len){
|
void AsyncEventSourceClient::write(const char* message, size_t len) {
|
||||||
_queueMessage(new AsyncEventSourceMessage(message, len));
|
if (!connected())
|
||||||
|
return;
|
||||||
|
_queueMessage(message, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AsyncEventSourceClient::send(const char *message, const char *event, uint32_t id, uint32_t reconnect){
|
void AsyncEventSourceClient::send(const char* message, const char* event, uint32_t id, uint32_t reconnect) {
|
||||||
|
if (!connected())
|
||||||
|
return;
|
||||||
String ev = generateEventMessage(message, event, id, reconnect);
|
String ev = generateEventMessage(message, event, id, reconnect);
|
||||||
_queueMessage(new AsyncEventSourceMessage(ev.c_str(), ev.length()));
|
_queueMessage(ev.c_str(), ev.length());
|
||||||
}
|
}
|
||||||
|
|
||||||
void AsyncEventSourceClient::_runQueue(){
|
size_t AsyncEventSourceClient::packetsWaiting() const {
|
||||||
while(!_messageQueue.isEmpty() && _messageQueue.front()->finished()){
|
#ifdef ESP32
|
||||||
_messageQueue.remove(_messageQueue.front());
|
std::lock_guard<std::mutex> lock(_lockmq);
|
||||||
}
|
#endif
|
||||||
|
return _messageQueue.size();
|
||||||
for(auto i = _messageQueue.begin(); i != _messageQueue.end(); ++i)
|
|
||||||
{
|
|
||||||
if(!(*i)->sent())
|
|
||||||
(*i)->send(_client);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AsyncEventSourceClient::_runQueue() {
|
||||||
|
size_t total_bytes_written = 0;
|
||||||
|
for (auto i = _messageQueue.begin(); i != _messageQueue.end(); ++i) {
|
||||||
|
if (!i->sent()) {
|
||||||
|
const size_t bytes_written = i->write(_client);
|
||||||
|
total_bytes_written += bytes_written;
|
||||||
|
if (bytes_written == 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (total_bytes_written > 0)
|
||||||
|
_client->send();
|
||||||
|
|
||||||
|
size_t len = total_bytes_written;
|
||||||
|
while (len && _messageQueue.size()) {
|
||||||
|
len = _messageQueue.front().ack(len);
|
||||||
|
if (_messageQueue.front().finished()) {
|
||||||
|
_messageQueue.pop_front();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Handler
|
// Handler
|
||||||
|
void AsyncEventSource::onConnect(ArEventHandlerFunction cb) {
|
||||||
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;
|
_connectcb = cb;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AsyncEventSource::_addClient(AsyncEventSourceClient * client){
|
void AsyncEventSource::authorizeConnect(ArAuthorizeConnectHandler cb) {
|
||||||
/*char * temp = (char *)malloc(2054);
|
AuthorizationMiddleware* m = new AuthorizationMiddleware(401, cb);
|
||||||
if(temp != NULL){
|
m->_freeOnRemoval = true;
|
||||||
memset(temp+1,' ',2048);
|
addMiddleware(m);
|
||||||
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);
|
|
||||||
}*/
|
|
||||||
|
|
||||||
_clients.add(client);
|
void AsyncEventSource::_addClient(AsyncEventSourceClient* client) {
|
||||||
if(_connectcb)
|
if (!client)
|
||||||
|
return;
|
||||||
|
#ifdef ESP32
|
||||||
|
std::lock_guard<std::mutex> lock(_client_queue_lock);
|
||||||
|
#endif
|
||||||
|
_clients.emplace_back(client);
|
||||||
|
if (_connectcb)
|
||||||
_connectcb(client);
|
_connectcb(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AsyncEventSource::_handleDisconnect(AsyncEventSourceClient * client){
|
void AsyncEventSource::_handleDisconnect(AsyncEventSourceClient* client) {
|
||||||
_clients.remove(client);
|
#ifdef ESP32
|
||||||
|
std::lock_guard<std::mutex> lock(_client_queue_lock);
|
||||||
|
#endif
|
||||||
|
for (auto i = _clients.begin(); i != _clients.end(); ++i) {
|
||||||
|
if (i->get() == client)
|
||||||
|
_clients.erase(i);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AsyncEventSource::close(){
|
void AsyncEventSource::close() {
|
||||||
for(const auto &c: _clients){
|
// While the whole loop is not done, the linked list is locked and so the
|
||||||
if(c->connected())
|
// iterator should remain valid even when AsyncEventSource::_handleDisconnect()
|
||||||
|
// is called very early
|
||||||
|
#ifdef ESP32
|
||||||
|
std::lock_guard<std::mutex> lock(_client_queue_lock);
|
||||||
|
#endif
|
||||||
|
for (const auto& c : _clients) {
|
||||||
|
if (c->connected())
|
||||||
c->close();
|
c->close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// pmb fix
|
// pmb fix
|
||||||
size_t AsyncEventSource::avgPacketsWaiting() const {
|
size_t AsyncEventSource::avgPacketsWaiting() const {
|
||||||
if(_clients.isEmpty())
|
size_t aql = 0;
|
||||||
|
uint32_t nConnectedClients = 0;
|
||||||
|
#ifdef ESP32
|
||||||
|
std::lock_guard<std::mutex> lock(_client_queue_lock);
|
||||||
|
#endif
|
||||||
|
if (!_clients.size())
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
size_t aql=0;
|
for (const auto& c : _clients) {
|
||||||
uint32_t nConnectedClients=0;
|
if (c->connected()) {
|
||||||
|
aql += c->packetsWaiting();
|
||||||
for(const auto &c: _clients){
|
|
||||||
if(c->connected()) {
|
|
||||||
aql+=c->packetsWaiting();
|
|
||||||
++nConnectedClients;
|
++nConnectedClients;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// return aql / nConnectedClients;
|
return ((aql) + (nConnectedClients / 2)) / (nConnectedClients); // round up
|
||||||
return ((aql) + (nConnectedClients/2))/(nConnectedClients); // round up
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AsyncEventSource::send(const char *message, const char *event, uint32_t id, uint32_t reconnect){
|
void AsyncEventSource::send(
|
||||||
|
const char* message, const char* event, uint32_t id, uint32_t reconnect) {
|
||||||
|
|
||||||
String ev = generateEventMessage(message, event, id, reconnect);
|
String ev = generateEventMessage(message, event, id, reconnect);
|
||||||
for(const auto &c: _clients){
|
#ifdef ESP32
|
||||||
if(c->connected()) {
|
std::lock_guard<std::mutex> lock(_client_queue_lock);
|
||||||
|
#endif
|
||||||
|
for (const auto& c : _clients) {
|
||||||
|
if (c->connected()) {
|
||||||
c->write(ev.c_str(), ev.length());
|
c->write(ev.c_str(), ev.length());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t AsyncEventSource::count() const {
|
size_t AsyncEventSource::count() const {
|
||||||
return _clients.count_if([](AsyncEventSourceClient *c){
|
#ifdef ESP32
|
||||||
return c->connected();
|
std::lock_guard<std::mutex> lock(_client_queue_lock);
|
||||||
});
|
#endif
|
||||||
|
size_t n_clients{0};
|
||||||
|
for (const auto& i : _clients)
|
||||||
|
if (i->connected())
|
||||||
|
++n_clients;
|
||||||
|
|
||||||
|
return n_clients;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AsyncEventSource::canHandle(AsyncWebServerRequest *request){
|
bool AsyncEventSource::canHandle(AsyncWebServerRequest* request) {
|
||||||
if(request->method() != HTTP_GET || !request->url().equals(_url)) {
|
if (request->method() != HTTP_GET || !request->url().equals(_url)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
request->addInterestingHeader("Last-Event-ID");
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AsyncEventSource::handleRequest(AsyncWebServerRequest *request){
|
void AsyncEventSource::handleRequest(AsyncWebServerRequest* request) {
|
||||||
if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str()))
|
|
||||||
return request->requestAuthentication();
|
|
||||||
request->send(new AsyncEventSourceResponse(this));
|
request->send(new AsyncEventSourceResponse(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Response
|
// Response
|
||||||
|
|
||||||
AsyncEventSourceResponse::AsyncEventSourceResponse(AsyncEventSource *server){
|
AsyncEventSourceResponse::AsyncEventSourceResponse(AsyncEventSource* server) {
|
||||||
_server = server;
|
_server = server;
|
||||||
_code = 200;
|
_code = 200;
|
||||||
_contentType = "text/event-stream";
|
_contentType = T_text_event_stream;
|
||||||
_sendContentLength = false;
|
_sendContentLength = false;
|
||||||
addHeader("Cache-Control", "no-cache");
|
addHeader(T_Cache_Control, T_no_cache);
|
||||||
addHeader("Connection","keep-alive");
|
addHeader(T_Connection, T_keep_alive);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AsyncEventSourceResponse::_respond(AsyncWebServerRequest *request){
|
void AsyncEventSourceResponse::_respond(AsyncWebServerRequest* request) {
|
||||||
String out = _assembleHead(request->version());
|
String out;
|
||||||
|
_assembleHead(out, request->version());
|
||||||
request->client()->write(out.c_str(), _headLength);
|
request->client()->write(out.c_str(), _headLength);
|
||||||
_state = RESPONSE_WAIT_ACK;
|
_state = RESPONSE_WAIT_ACK;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t AsyncEventSourceResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time __attribute__((unused))){
|
size_t AsyncEventSourceResponse::_ack(AsyncWebServerRequest* request, size_t len, uint32_t time __attribute__((unused))) {
|
||||||
if(len){
|
if (len) {
|
||||||
new AsyncEventSourceClient(request, _server);
|
new AsyncEventSourceClient(request, _server);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,117 +21,133 @@
|
|||||||
#define ASYNCEVENTSOURCE_H_
|
#define ASYNCEVENTSOURCE_H_
|
||||||
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <Arduino.h>
|
#ifdef ESP32
|
||||||
#if defined(ESP32) || defined(LIBRETINY)
|
#include <AsyncTCP.h>
|
||||||
#include <AsyncTCP.h>
|
#include <mutex>
|
||||||
#else
|
#ifndef SSE_MAX_QUEUED_MESSAGES
|
||||||
#include <ESPAsyncTCP.h>
|
#define SSE_MAX_QUEUED_MESSAGES 32
|
||||||
#endif
|
#endif
|
||||||
|
#elif defined(ESP8266)
|
||||||
#ifndef SSE_MAX_QUEUED_MESSAGES
|
#include <ESPAsyncTCP.h>
|
||||||
#define SSE_MAX_QUEUED_MESSAGES 32
|
#ifndef SSE_MAX_QUEUED_MESSAGES
|
||||||
|
#define SSE_MAX_QUEUED_MESSAGES 8
|
||||||
|
#endif
|
||||||
|
#elif defined(TARGET_RP2040)
|
||||||
|
#include <AsyncTCP_RP2040W.h>
|
||||||
|
#ifndef SSE_MAX_QUEUED_MESSAGES
|
||||||
|
#define SSE_MAX_QUEUED_MESSAGES 32
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <ESPAsyncWebServer.h>
|
#include <ESPAsyncWebServer.h>
|
||||||
|
|
||||||
#include "AsyncWebSynchronization.h"
|
|
||||||
|
|
||||||
#ifdef ESP8266
|
#ifdef ESP8266
|
||||||
#include <Hash.h>
|
#include <Hash.h>
|
||||||
#ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library
|
#ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library
|
||||||
#include <../src/Hash.h>
|
#include <../src/Hash.h>
|
||||||
#endif
|
#endif
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(ESP32) || defined(LIBRETINY)
|
|
||||||
#define DEFAULT_MAX_SSE_CLIENTS 8
|
|
||||||
#else
|
|
||||||
#define DEFAULT_MAX_SSE_CLIENTS 4
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
class AsyncEventSource;
|
class AsyncEventSource;
|
||||||
class AsyncEventSourceResponse;
|
class AsyncEventSourceResponse;
|
||||||
class AsyncEventSourceClient;
|
class AsyncEventSourceClient;
|
||||||
typedef std::function<void(AsyncEventSourceClient *client)> ArEventHandlerFunction;
|
using ArEventHandlerFunction = std::function<void(AsyncEventSourceClient* client)>;
|
||||||
|
using ArAuthorizeConnectHandler = ArAuthorizeFunction;
|
||||||
|
|
||||||
class AsyncEventSourceMessage {
|
class AsyncEventSourceMessage {
|
||||||
private:
|
private:
|
||||||
uint8_t * _data;
|
uint8_t* _data;
|
||||||
size_t _len;
|
size_t _len;
|
||||||
size_t _sent;
|
size_t _sent;
|
||||||
//size_t _ack;
|
// size_t _ack;
|
||||||
size_t _acked;
|
size_t _acked;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AsyncEventSourceMessage(const char * data, size_t len);
|
AsyncEventSourceMessage(const char* data, size_t len);
|
||||||
~AsyncEventSourceMessage();
|
~AsyncEventSourceMessage();
|
||||||
size_t ack(size_t len, uint32_t time __attribute__((unused)));
|
size_t ack(size_t len, uint32_t time = 0);
|
||||||
size_t send(AsyncClient *client);
|
size_t write(AsyncClient* client);
|
||||||
bool finished(){ return _acked == _len; }
|
size_t send(AsyncClient* client);
|
||||||
|
bool finished() { return _acked == _len; }
|
||||||
bool sent() { return _sent == _len; }
|
bool sent() { return _sent == _len; }
|
||||||
};
|
};
|
||||||
|
|
||||||
class AsyncEventSourceClient {
|
class AsyncEventSourceClient {
|
||||||
private:
|
private:
|
||||||
AsyncClient *_client;
|
AsyncClient* _client;
|
||||||
AsyncEventSource *_server;
|
AsyncEventSource* _server;
|
||||||
uint32_t _lastId;
|
uint32_t _lastId;
|
||||||
LinkedList<AsyncEventSourceMessage *> _messageQueue;
|
std::list<AsyncEventSourceMessage> _messageQueue;
|
||||||
void _queueMessage(AsyncEventSourceMessage *dataMessage);
|
#ifdef ESP32
|
||||||
|
mutable std::mutex _lockmq;
|
||||||
|
#endif
|
||||||
|
void _queueMessage(const char* message, size_t len);
|
||||||
void _runQueue();
|
void _runQueue();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
AsyncEventSourceClient(AsyncWebServerRequest* request, AsyncEventSource* server);
|
||||||
AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server);
|
|
||||||
~AsyncEventSourceClient();
|
~AsyncEventSourceClient();
|
||||||
|
|
||||||
AsyncClient* client(){ return _client; }
|
AsyncClient* client() { return _client; }
|
||||||
void close();
|
void close();
|
||||||
void write(const char * message, size_t len);
|
void write(const char* message, size_t len);
|
||||||
void send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0);
|
void send(const String& message, const String& event, uint32_t id = 0, uint32_t reconnect = 0) { send(message.c_str(), event.c_str(), id, reconnect); }
|
||||||
|
void send(const String& message, const char* event, uint32_t id = 0, uint32_t reconnect = 0) { send(message.c_str(), event, id, reconnect); }
|
||||||
|
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(); }
|
bool connected() const { return (_client != NULL) && _client->connected(); }
|
||||||
uint32_t lastId() const { return _lastId; }
|
uint32_t lastId() const { return _lastId; }
|
||||||
size_t packetsWaiting() const { return _messageQueue.length(); }
|
size_t packetsWaiting() const;
|
||||||
|
|
||||||
//system callbacks (do not call)
|
// system callbacks (do not call)
|
||||||
void _onAck(size_t len, uint32_t time);
|
void _onAck(size_t len, uint32_t time);
|
||||||
void _onPoll();
|
void _onPoll();
|
||||||
void _onTimeout(uint32_t time);
|
void _onTimeout(uint32_t time);
|
||||||
void _onDisconnect();
|
void _onDisconnect();
|
||||||
};
|
};
|
||||||
|
|
||||||
class AsyncEventSource: public AsyncWebHandler {
|
class AsyncEventSource : public AsyncWebHandler {
|
||||||
private:
|
private:
|
||||||
String _url;
|
String _url;
|
||||||
LinkedList<AsyncEventSourceClient *> _clients;
|
std::list<std::unique_ptr<AsyncEventSourceClient>> _clients;
|
||||||
ArEventHandlerFunction _connectcb;
|
#ifdef ESP32
|
||||||
public:
|
// Same as for individual messages, protect mutations of _clients list
|
||||||
AsyncEventSource(const String& url);
|
// since simultaneous access from different tasks is possible
|
||||||
~AsyncEventSource();
|
mutable std::mutex _client_queue_lock;
|
||||||
|
#endif
|
||||||
|
ArEventHandlerFunction _connectcb{nullptr};
|
||||||
|
|
||||||
const char * url() const { return _url.c_str(); }
|
public:
|
||||||
|
AsyncEventSource(const String& url) : _url(url) {};
|
||||||
|
~AsyncEventSource() { close(); };
|
||||||
|
|
||||||
|
const char* url() const { return _url.c_str(); }
|
||||||
void close();
|
void close();
|
||||||
void onConnect(ArEventHandlerFunction cb);
|
void onConnect(ArEventHandlerFunction cb);
|
||||||
void send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0);
|
void authorizeConnect(ArAuthorizeConnectHandler cb);
|
||||||
size_t count() const; //number clinets connected
|
void send(const String& message, const String& event, uint32_t id = 0, uint32_t reconnect = 0) { send(message.c_str(), event.c_str(), id, reconnect); }
|
||||||
size_t avgPacketsWaiting() const;
|
void send(const String& message, const char* event, uint32_t id = 0, uint32_t reconnect = 0) { send(message.c_str(), event, id, reconnect); }
|
||||||
|
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)
|
// system callbacks (do not call)
|
||||||
void _addClient(AsyncEventSourceClient * client);
|
void _addClient(AsyncEventSourceClient* client);
|
||||||
void _handleDisconnect(AsyncEventSourceClient * client);
|
void _handleDisconnect(AsyncEventSourceClient* client);
|
||||||
virtual bool canHandle(AsyncWebServerRequest *request) override final;
|
virtual bool canHandle(AsyncWebServerRequest* request) override final;
|
||||||
virtual void handleRequest(AsyncWebServerRequest *request) override final;
|
virtual void handleRequest(AsyncWebServerRequest* request) override final;
|
||||||
};
|
};
|
||||||
|
|
||||||
class AsyncEventSourceResponse: public AsyncWebServerResponse {
|
class AsyncEventSourceResponse : public AsyncWebServerResponse {
|
||||||
private:
|
private:
|
||||||
String _content;
|
String _content;
|
||||||
AsyncEventSource *_server;
|
AsyncEventSource* _server;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AsyncEventSourceResponse(AsyncEventSource *server);
|
AsyncEventSourceResponse(AsyncEventSource* server);
|
||||||
void _respond(AsyncWebServerRequest *request);
|
void _respond(AsyncWebServerRequest* request);
|
||||||
size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time);
|
size_t _ack(AsyncWebServerRequest* request, size_t len, uint32_t time);
|
||||||
bool _sourceValid() const { return true; }
|
bool _sourceValid() const { return true; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
#endif /* ASYNCEVENTSOURCE_H_ */
|
#endif /* ASYNCEVENTSOURCE_H_ */
|
||||||
|
155
src/AsyncJson.cpp
Normal file
155
src/AsyncJson.cpp
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
#include "AsyncJson.h"
|
||||||
|
|
||||||
|
#if ASYNC_JSON_SUPPORT == 1
|
||||||
|
|
||||||
|
#if ARDUINOJSON_VERSION_MAJOR == 5
|
||||||
|
AsyncJsonResponse::AsyncJsonResponse(bool isArray) : _isValid{false} {
|
||||||
|
_code = 200;
|
||||||
|
_contentType = asyncsrv::T_application_json;
|
||||||
|
if (isArray)
|
||||||
|
_root = _jsonBuffer.createArray();
|
||||||
|
else
|
||||||
|
_root = _jsonBuffer.createObject();
|
||||||
|
}
|
||||||
|
#elif ARDUINOJSON_VERSION_MAJOR == 6
|
||||||
|
AsyncJsonResponse::AsyncJsonResponse(bool isArray, size_t maxJsonBufferSize) : _jsonBuffer(maxJsonBufferSize), _isValid{false} {
|
||||||
|
_code = 200;
|
||||||
|
_contentType = asyncsrv::T_application_json;
|
||||||
|
if (isArray)
|
||||||
|
_root = _jsonBuffer.createNestedArray();
|
||||||
|
else
|
||||||
|
_root = _jsonBuffer.createNestedObject();
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
AsyncJsonResponse::AsyncJsonResponse(bool isArray) : _isValid{false} {
|
||||||
|
_code = 200;
|
||||||
|
_contentType = asyncsrv::T_application_json;
|
||||||
|
if (isArray)
|
||||||
|
_root = _jsonBuffer.add<JsonArray>();
|
||||||
|
else
|
||||||
|
_root = _jsonBuffer.add<JsonObject>();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
size_t AsyncJsonResponse::setLength() {
|
||||||
|
#if ARDUINOJSON_VERSION_MAJOR == 5
|
||||||
|
_contentLength = _root.measureLength();
|
||||||
|
#else
|
||||||
|
_contentLength = measureJson(_root);
|
||||||
|
#endif
|
||||||
|
if (_contentLength) {
|
||||||
|
_isValid = true;
|
||||||
|
}
|
||||||
|
return _contentLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t AsyncJsonResponse::_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;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if ARDUINOJSON_VERSION_MAJOR == 6
|
||||||
|
PrettyAsyncJsonResponse::PrettyAsyncJsonResponse(bool isArray, size_t maxJsonBufferSize) : AsyncJsonResponse{isArray, maxJsonBufferSize} {}
|
||||||
|
#else
|
||||||
|
PrettyAsyncJsonResponse::PrettyAsyncJsonResponse(bool isArray) : AsyncJsonResponse{isArray} {}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
size_t PrettyAsyncJsonResponse::setLength() {
|
||||||
|
#if ARDUINOJSON_VERSION_MAJOR == 5
|
||||||
|
_contentLength = _root.measurePrettyLength();
|
||||||
|
#else
|
||||||
|
_contentLength = measureJsonPretty(_root);
|
||||||
|
#endif
|
||||||
|
if (_contentLength) {
|
||||||
|
_isValid = true;
|
||||||
|
}
|
||||||
|
return _contentLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t PrettyAsyncJsonResponse::_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;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if ARDUINOJSON_VERSION_MAJOR == 6
|
||||||
|
AsyncCallbackJsonWebHandler::AsyncCallbackJsonWebHandler(const String& uri, ArJsonRequestHandlerFunction onRequest, size_t maxJsonBufferSize)
|
||||||
|
: _uri(uri), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), maxJsonBufferSize(maxJsonBufferSize), _maxContentLength(16384) {}
|
||||||
|
#else
|
||||||
|
AsyncCallbackJsonWebHandler::AsyncCallbackJsonWebHandler(const String& uri, ArJsonRequestHandlerFunction onRequest)
|
||||||
|
: _uri(uri), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), _maxContentLength(16384) {}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool AsyncCallbackJsonWebHandler::canHandle(AsyncWebServerRequest* request) {
|
||||||
|
if (!_onRequest)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
WebRequestMethodComposite request_method = request->method();
|
||||||
|
if (!(_method & request_method))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri + "/")))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (request_method != HTTP_GET && !request->contentType().equalsIgnoreCase(asyncsrv::T_application_json))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncCallbackJsonWebHandler::handleRequest(AsyncWebServerRequest* request) {
|
||||||
|
if (_onRequest) {
|
||||||
|
if (request->method() == HTTP_GET) {
|
||||||
|
JsonVariant json;
|
||||||
|
_onRequest(request, json);
|
||||||
|
return;
|
||||||
|
} else 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncCallbackJsonWebHandler::handleBody(AsyncWebServerRequest* request, uint8_t* data, size_t len, size_t index, size_t total) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // ASYNC_JSON_SUPPORT
|
273
src/AsyncJson.h
273
src/AsyncJson.h
@ -26,7 +26,7 @@
|
|||||||
|
|
||||||
AsyncCallbackJsonWebHandler* handler = new AsyncCallbackJsonWebHandler("/rest/endpoint");
|
AsyncCallbackJsonWebHandler* handler = new AsyncCallbackJsonWebHandler("/rest/endpoint");
|
||||||
handler->onRequest([](AsyncWebServerRequest *request, JsonVariant &json) {
|
handler->onRequest([](AsyncWebServerRequest *request, JsonVariant &json) {
|
||||||
JsonObject& jsonObj = json.as<JsonObject>();
|
JsonObject jsonObj = json.as<JsonObject>();
|
||||||
// ...
|
// ...
|
||||||
});
|
});
|
||||||
server.addHandler(handler);
|
server.addHandler(handler);
|
||||||
@ -34,221 +34,98 @@
|
|||||||
*/
|
*/
|
||||||
#ifndef ASYNC_JSON_H_
|
#ifndef ASYNC_JSON_H_
|
||||||
#define ASYNC_JSON_H_
|
#define ASYNC_JSON_H_
|
||||||
#include <ArduinoJson.h>
|
|
||||||
#include <ESPAsyncWebServer.h>
|
|
||||||
#include <Print.h>
|
|
||||||
|
|
||||||
#if ARDUINOJSON_VERSION_MAJOR == 5
|
#if __has_include("ArduinoJson.h")
|
||||||
#define ARDUINOJSON_5_COMPATIBILITY
|
#include <ArduinoJson.h>
|
||||||
#else
|
#if ARDUINOJSON_VERSION_MAJOR >= 5
|
||||||
#ifndef DYNAMIC_JSON_DOCUMENT_SIZE
|
#define ASYNC_JSON_SUPPORT 1
|
||||||
#define DYNAMIC_JSON_DOCUMENT_SIZE 1024
|
#else
|
||||||
|
#define ASYNC_JSON_SUPPORT 0
|
||||||
|
#endif // ARDUINOJSON_VERSION_MAJOR >= 5
|
||||||
|
#endif // __has_include("ArduinoJson.h")
|
||||||
|
|
||||||
|
#if ASYNC_JSON_SUPPORT == 1
|
||||||
|
#include <ESPAsyncWebServer.h>
|
||||||
|
|
||||||
|
#include "ChunkPrint.h"
|
||||||
|
|
||||||
|
#if ARDUINOJSON_VERSION_MAJOR == 6
|
||||||
|
#ifndef DYNAMIC_JSON_DOCUMENT_SIZE
|
||||||
|
#define DYNAMIC_JSON_DOCUMENT_SIZE 1024
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
#endif
|
|
||||||
|
|
||||||
constexpr const char* JSON_MIMETYPE = "application/json";
|
class AsyncJsonResponse : public AsyncAbstractResponse {
|
||||||
|
|
||||||
/*
|
|
||||||
* 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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class AsyncJsonResponse: public AsyncAbstractResponse {
|
|
||||||
protected:
|
protected:
|
||||||
|
#if ARDUINOJSON_VERSION_MAJOR == 5
|
||||||
#ifdef ARDUINOJSON_5_COMPATIBILITY
|
|
||||||
DynamicJsonBuffer _jsonBuffer;
|
DynamicJsonBuffer _jsonBuffer;
|
||||||
#else
|
#elif ARDUINOJSON_VERSION_MAJOR == 6
|
||||||
DynamicJsonDocument _jsonBuffer;
|
DynamicJsonDocument _jsonBuffer;
|
||||||
#endif
|
#else
|
||||||
|
JsonDocument _jsonBuffer;
|
||||||
|
#endif
|
||||||
|
|
||||||
JsonVariant _root;
|
JsonVariant _root;
|
||||||
bool _isValid;
|
bool _isValid;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
#if ARDUINOJSON_VERSION_MAJOR == 6
|
||||||
#ifdef ARDUINOJSON_5_COMPATIBILITY
|
AsyncJsonResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE);
|
||||||
AsyncJsonResponse(bool isArray=false): _isValid{false} {
|
#else
|
||||||
_code = 200;
|
AsyncJsonResponse(bool isArray = false);
|
||||||
_contentType = JSON_MIMETYPE;
|
#endif
|
||||||
if(isArray)
|
JsonVariant& getRoot() { return _root; }
|
||||||
_root = _jsonBuffer.createArray();
|
|
||||||
else
|
|
||||||
_root = _jsonBuffer.createObject();
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
~AsyncJsonResponse() {}
|
|
||||||
JsonVariant & getRoot() { return _root; }
|
|
||||||
bool _sourceValid() const { return _isValid; }
|
bool _sourceValid() const { return _isValid; }
|
||||||
size_t setLength() {
|
size_t setLength();
|
||||||
|
size_t getSize() const { return _jsonBuffer.size(); }
|
||||||
#ifdef ARDUINOJSON_5_COMPATIBILITY
|
size_t _fillBuffer(uint8_t* data, size_t len);
|
||||||
_contentLength = _root.measureLength();
|
#if ARDUINOJSON_VERSION_MAJOR >= 6
|
||||||
#else
|
bool overflowed() const { return _jsonBuffer.overflowed(); }
|
||||||
_contentLength = measureJson(_root);
|
#endif
|
||||||
#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);
|
|
||||||
|
|
||||||
#ifdef ARDUINOJSON_5_COMPATIBILITY
|
|
||||||
_root.printTo( dest ) ;
|
|
||||||
#else
|
|
||||||
serializeJson(_root, dest);
|
|
||||||
#endif
|
|
||||||
return len;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class PrettyAsyncJsonResponse: public AsyncJsonResponse {
|
class PrettyAsyncJsonResponse : public AsyncJsonResponse {
|
||||||
public:
|
public:
|
||||||
#ifdef ARDUINOJSON_5_COMPATIBILITY
|
#if ARDUINOJSON_VERSION_MAJOR == 6
|
||||||
PrettyAsyncJsonResponse (bool isArray=false) : AsyncJsonResponse{isArray} {}
|
PrettyAsyncJsonResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE);
|
||||||
#else
|
#else
|
||||||
PrettyAsyncJsonResponse (bool isArray=false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE) : AsyncJsonResponse{isArray, maxJsonBufferSize} {}
|
PrettyAsyncJsonResponse(bool isArray = false);
|
||||||
#endif
|
#endif
|
||||||
size_t setLength () {
|
size_t setLength();
|
||||||
#ifdef ARDUINOJSON_5_COMPATIBILITY
|
size_t _fillBuffer(uint8_t* data, size_t len);
|
||||||
_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);
|
|
||||||
#ifdef ARDUINOJSON_5_COMPATIBILITY
|
|
||||||
_root.prettyPrintTo (dest);
|
|
||||||
#else
|
|
||||||
serializeJsonPretty(_root, dest);
|
|
||||||
#endif
|
|
||||||
return len;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef std::function<void(AsyncWebServerRequest *request, JsonVariant &json)> ArJsonRequestHandlerFunction;
|
typedef std::function<void(AsyncWebServerRequest* request, JsonVariant& json)> ArJsonRequestHandlerFunction;
|
||||||
|
|
||||||
class AsyncCallbackJsonWebHandler: public AsyncWebHandler {
|
class AsyncCallbackJsonWebHandler : public AsyncWebHandler {
|
||||||
private:
|
protected:
|
||||||
protected:
|
String _uri;
|
||||||
const String _uri;
|
WebRequestMethodComposite _method;
|
||||||
WebRequestMethodComposite _method;
|
ArJsonRequestHandlerFunction _onRequest;
|
||||||
ArJsonRequestHandlerFunction _onRequest;
|
size_t _contentLength;
|
||||||
size_t _contentLength;
|
#if ARDUINOJSON_VERSION_MAJOR == 6
|
||||||
#ifndef ARDUINOJSON_5_COMPATIBILITY
|
size_t maxJsonBufferSize;
|
||||||
const size_t maxJsonBufferSize;
|
#endif
|
||||||
#endif
|
size_t _maxContentLength;
|
||||||
size_t _maxContentLength;
|
|
||||||
public:
|
|
||||||
#ifdef ARDUINOJSON_5_COMPATIBILITY
|
|
||||||
AsyncCallbackJsonWebHandler(const String& uri, ArJsonRequestHandlerFunction onRequest)
|
|
||||||
: _uri(uri), _method(HTTP_POST|HTTP_PUT|HTTP_PATCH), _onRequest(onRequest), _maxContentLength(16384) {}
|
|
||||||
#else
|
|
||||||
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) {}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void setMethod(WebRequestMethodComposite method){ _method = method; }
|
public:
|
||||||
void setMaxContentLength(int maxContentLength){ _maxContentLength = maxContentLength; }
|
#if ARDUINOJSON_VERSION_MAJOR == 6
|
||||||
void onRequest(ArJsonRequestHandlerFunction fn){ _onRequest = fn; }
|
AsyncCallbackJsonWebHandler(const String& uri, ArJsonRequestHandlerFunction onRequest = nullptr, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE);
|
||||||
|
#else
|
||||||
|
AsyncCallbackJsonWebHandler(const String& uri, ArJsonRequestHandlerFunction onRequest = nullptr);
|
||||||
|
#endif
|
||||||
|
|
||||||
virtual bool canHandle(AsyncWebServerRequest *request) override final{
|
void setMethod(WebRequestMethodComposite method) { _method = method; }
|
||||||
if(!_onRequest)
|
void setMaxContentLength(int maxContentLength) { _maxContentLength = maxContentLength; }
|
||||||
return false;
|
void onRequest(ArJsonRequestHandlerFunction fn) { _onRequest = fn; }
|
||||||
|
|
||||||
if(!(_method & request->method()))
|
virtual bool canHandle(AsyncWebServerRequest* request) override final;
|
||||||
return false;
|
virtual void handleRequest(AsyncWebServerRequest* request) override final;
|
||||||
|
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) override final {}
|
||||||
if(_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri+"/")))
|
virtual void handleBody(AsyncWebServerRequest* request, uint8_t* data, size_t len, size_t index, size_t total) override final;
|
||||||
return false;
|
virtual bool isRequestHandlerTrivial() override final { return !_onRequest; }
|
||||||
|
|
||||||
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) {
|
|
||||||
|
|
||||||
#ifdef ARDUINOJSON_5_COMPATIBILITY
|
|
||||||
DynamicJsonBuffer jsonBuffer;
|
|
||||||
JsonVariant json = jsonBuffer.parse((uint8_t*)(request->_tempObject));
|
|
||||||
if (json.success()) {
|
|
||||||
#else
|
|
||||||
DynamicJsonDocument jsonBuffer(this->maxJsonBufferSize);
|
|
||||||
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
|
|
||||||
|
#endif // ASYNC_JSON_SUPPORT == 1
|
||||||
|
|
||||||
|
#endif // ASYNC_JSON_H_
|
||||||
|
106
src/AsyncMessagePack.cpp
Normal file
106
src/AsyncMessagePack.cpp
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
#include "AsyncMessagePack.h"
|
||||||
|
|
||||||
|
#if ASYNC_MSG_PACK_SUPPORT == 1
|
||||||
|
|
||||||
|
#if ARDUINOJSON_VERSION_MAJOR == 6
|
||||||
|
AsyncMessagePackResponse::AsyncMessagePackResponse(bool isArray, size_t maxJsonBufferSize) : _jsonBuffer(maxJsonBufferSize), _isValid{false} {
|
||||||
|
_code = 200;
|
||||||
|
_contentType = asyncsrv::T_application_msgpack;
|
||||||
|
if (isArray)
|
||||||
|
_root = _jsonBuffer.createNestedArray();
|
||||||
|
else
|
||||||
|
_root = _jsonBuffer.createNestedObject();
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
AsyncMessagePackResponse::AsyncMessagePackResponse(bool isArray) : _isValid{false} {
|
||||||
|
_code = 200;
|
||||||
|
_contentType = asyncsrv::T_application_msgpack;
|
||||||
|
if (isArray)
|
||||||
|
_root = _jsonBuffer.add<JsonArray>();
|
||||||
|
else
|
||||||
|
_root = _jsonBuffer.add<JsonObject>();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
size_t AsyncMessagePackResponse::setLength() {
|
||||||
|
_contentLength = measureMsgPack(_root);
|
||||||
|
if (_contentLength) {
|
||||||
|
_isValid = true;
|
||||||
|
}
|
||||||
|
return _contentLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t AsyncMessagePackResponse::_fillBuffer(uint8_t* data, size_t len) {
|
||||||
|
ChunkPrint dest(data, _sentLength, len);
|
||||||
|
serializeMsgPack(_root, dest);
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if ARDUINOJSON_VERSION_MAJOR == 6
|
||||||
|
AsyncCallbackMessagePackWebHandler::AsyncCallbackMessagePackWebHandler(const String& uri, ArMessagePackRequestHandlerFunction onRequest, size_t maxJsonBufferSize)
|
||||||
|
: _uri(uri), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), maxJsonBufferSize(maxJsonBufferSize), _maxContentLength(16384) {}
|
||||||
|
#else
|
||||||
|
AsyncCallbackMessagePackWebHandler::AsyncCallbackMessagePackWebHandler(const String& uri, ArMessagePackRequestHandlerFunction onRequest)
|
||||||
|
: _uri(uri), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), _maxContentLength(16384) {}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool AsyncCallbackMessagePackWebHandler::canHandle(AsyncWebServerRequest* request) {
|
||||||
|
if (!_onRequest)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
WebRequestMethodComposite request_method = request->method();
|
||||||
|
if (!(_method & request_method))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri + "/")))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (request_method != HTTP_GET && !request->contentType().equalsIgnoreCase(asyncsrv::T_application_msgpack))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncCallbackMessagePackWebHandler::handleRequest(AsyncWebServerRequest* request) {
|
||||||
|
if (_onRequest) {
|
||||||
|
if (request->method() == HTTP_GET) {
|
||||||
|
JsonVariant json;
|
||||||
|
_onRequest(request, json);
|
||||||
|
return;
|
||||||
|
} else if (request->_tempObject != NULL) {
|
||||||
|
|
||||||
|
#if ARDUINOJSON_VERSION_MAJOR == 6
|
||||||
|
DynamicJsonDocument jsonBuffer(this->maxJsonBufferSize);
|
||||||
|
DeserializationError error = deserializeMsgPack(jsonBuffer, (uint8_t*)(request->_tempObject));
|
||||||
|
if (!error) {
|
||||||
|
JsonVariant json = jsonBuffer.as<JsonVariant>();
|
||||||
|
#else
|
||||||
|
JsonDocument jsonBuffer;
|
||||||
|
DeserializationError error = deserializeMsgPack(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncCallbackMessagePackWebHandler::handleBody(AsyncWebServerRequest* request, uint8_t* data, size_t len, size_t index, size_t total) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // ASYNC_MSG_PACK_SUPPORT
|
102
src/AsyncMessagePack.h
Normal file
102
src/AsyncMessagePack.h
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
/*
|
||||||
|
server.on("/msg_pack", HTTP_ANY, [](AsyncWebServerRequest * request) {
|
||||||
|
AsyncMessagePackResponse * response = new AsyncMessagePackResponse();
|
||||||
|
JsonObject& root = response->getRoot();
|
||||||
|
root["key1"] = "key number one";
|
||||||
|
JsonObject& nested = root.createNestedObject("nested");
|
||||||
|
nested["key1"] = "key number one";
|
||||||
|
response->setLength();
|
||||||
|
request->send(response);
|
||||||
|
});
|
||||||
|
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
AsyncCallbackMessagePackWebHandler* handler = new AsyncCallbackMessagePackWebHandler("/msg_pack/endpoint");
|
||||||
|
handler->onRequest([](AsyncWebServerRequest *request, JsonVariant &json) {
|
||||||
|
JsonObject jsonObj = json.as<JsonObject>();
|
||||||
|
// ...
|
||||||
|
});
|
||||||
|
server.addHandler(handler);
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if __has_include("ArduinoJson.h")
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
#if ARDUINOJSON_VERSION_MAJOR >= 6
|
||||||
|
#define ASYNC_MSG_PACK_SUPPORT 1
|
||||||
|
#else
|
||||||
|
#define ASYNC_MSG_PACK_SUPPORT 0
|
||||||
|
#endif // ARDUINOJSON_VERSION_MAJOR >= 6
|
||||||
|
#endif // __has_include("ArduinoJson.h")
|
||||||
|
|
||||||
|
#if ASYNC_MSG_PACK_SUPPORT == 1
|
||||||
|
#include <ESPAsyncWebServer.h>
|
||||||
|
|
||||||
|
#include "ChunkPrint.h"
|
||||||
|
|
||||||
|
#if ARDUINOJSON_VERSION_MAJOR == 6
|
||||||
|
#ifndef DYNAMIC_JSON_DOCUMENT_SIZE
|
||||||
|
#define DYNAMIC_JSON_DOCUMENT_SIZE 1024
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class AsyncMessagePackResponse : public AsyncAbstractResponse {
|
||||||
|
protected:
|
||||||
|
#if ARDUINOJSON_VERSION_MAJOR == 6
|
||||||
|
DynamicJsonDocument _jsonBuffer;
|
||||||
|
#else
|
||||||
|
JsonDocument _jsonBuffer;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
JsonVariant _root;
|
||||||
|
bool _isValid;
|
||||||
|
|
||||||
|
public:
|
||||||
|
#if ARDUINOJSON_VERSION_MAJOR == 6
|
||||||
|
AsyncMessagePackResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE);
|
||||||
|
#else
|
||||||
|
AsyncMessagePackResponse(bool isArray = false);
|
||||||
|
#endif
|
||||||
|
JsonVariant& getRoot() { return _root; }
|
||||||
|
bool _sourceValid() const { return _isValid; }
|
||||||
|
size_t setLength();
|
||||||
|
size_t getSize() const { return _jsonBuffer.size(); }
|
||||||
|
size_t _fillBuffer(uint8_t* data, size_t len);
|
||||||
|
#if ARDUINOJSON_VERSION_MAJOR >= 6
|
||||||
|
bool overflowed() const { return _jsonBuffer.overflowed(); }
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef std::function<void(AsyncWebServerRequest* request, JsonVariant& json)> ArMessagePackRequestHandlerFunction;
|
||||||
|
|
||||||
|
class AsyncCallbackMessagePackWebHandler : public AsyncWebHandler {
|
||||||
|
protected:
|
||||||
|
String _uri;
|
||||||
|
WebRequestMethodComposite _method;
|
||||||
|
ArMessagePackRequestHandlerFunction _onRequest;
|
||||||
|
size_t _contentLength;
|
||||||
|
#if ARDUINOJSON_VERSION_MAJOR == 6
|
||||||
|
size_t maxJsonBufferSize;
|
||||||
|
#endif
|
||||||
|
size_t _maxContentLength;
|
||||||
|
|
||||||
|
public:
|
||||||
|
#if ARDUINOJSON_VERSION_MAJOR == 6
|
||||||
|
AsyncCallbackMessagePackWebHandler(const String& uri, ArMessagePackRequestHandlerFunction onRequest = nullptr, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE);
|
||||||
|
#else
|
||||||
|
AsyncCallbackMessagePackWebHandler(const String& uri, ArMessagePackRequestHandlerFunction onRequest = nullptr);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void setMethod(WebRequestMethodComposite method) { _method = method; }
|
||||||
|
void setMaxContentLength(int maxContentLength) { _maxContentLength = maxContentLength; }
|
||||||
|
void onRequest(ArMessagePackRequestHandlerFunction fn) { _onRequest = fn; }
|
||||||
|
|
||||||
|
virtual bool canHandle(AsyncWebServerRequest* request) override final;
|
||||||
|
virtual void handleRequest(AsyncWebServerRequest* request) override final;
|
||||||
|
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) override final {}
|
||||||
|
virtual void handleBody(AsyncWebServerRequest* request, uint8_t* data, size_t len, size_t index, size_t total) override final;
|
||||||
|
virtual bool isRequestHandlerTrivial() override final { return !_onRequest; }
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // ASYNC_MSG_PACK_SUPPORT == 1
|
22
src/AsyncWebHeader.cpp
Normal file
22
src/AsyncWebHeader.cpp
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
#include <ESPAsyncWebServer.h>
|
||||||
|
|
||||||
|
AsyncWebHeader::AsyncWebHeader(const String& data) {
|
||||||
|
if (!data)
|
||||||
|
return;
|
||||||
|
int index = data.indexOf(':');
|
||||||
|
if (index < 0)
|
||||||
|
return;
|
||||||
|
_name = data.substring(0, index);
|
||||||
|
_value = data.substring(index + 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
String AsyncWebHeader::toString() const {
|
||||||
|
String str;
|
||||||
|
str.reserve(_name.length() + _value.length() + 2);
|
||||||
|
str.concat(_name);
|
||||||
|
str.concat((char)0x3a);
|
||||||
|
str.concat((char)0x20);
|
||||||
|
str.concat(_value);
|
||||||
|
str.concat(asyncsrv::T_rn);
|
||||||
|
return str;
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -22,34 +22,45 @@
|
|||||||
#define ASYNCWEBSOCKET_H_
|
#define ASYNCWEBSOCKET_H_
|
||||||
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#if defined(ESP32) || defined(LIBRETINY)
|
#ifdef ESP32
|
||||||
#include <AsyncTCP.h>
|
#include <AsyncTCP.h>
|
||||||
#ifndef WS_MAX_QUEUED_MESSAGES
|
#include <mutex>
|
||||||
#define WS_MAX_QUEUED_MESSAGES 32
|
#ifndef WS_MAX_QUEUED_MESSAGES
|
||||||
#endif
|
#define WS_MAX_QUEUED_MESSAGES 32
|
||||||
#else
|
#endif
|
||||||
#include <ESPAsyncTCP.h>
|
#elif defined(ESP8266)
|
||||||
#ifndef WS_MAX_QUEUED_MESSAGES
|
#include <ESPAsyncTCP.h>
|
||||||
#define WS_MAX_QUEUED_MESSAGES 8
|
#ifndef WS_MAX_QUEUED_MESSAGES
|
||||||
#endif
|
#define WS_MAX_QUEUED_MESSAGES 8
|
||||||
|
#endif
|
||||||
|
#elif defined(TARGET_RP2040)
|
||||||
|
#include <AsyncTCP_RP2040W.h>
|
||||||
|
#ifndef WS_MAX_QUEUED_MESSAGES
|
||||||
|
#define WS_MAX_QUEUED_MESSAGES 32
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <ESPAsyncWebServer.h>
|
#include <ESPAsyncWebServer.h>
|
||||||
|
|
||||||
#include "AsyncWebSynchronization.h"
|
#include <memory>
|
||||||
|
|
||||||
#ifdef ESP8266
|
#ifdef ESP8266
|
||||||
#include <Hash.h>
|
#include <Hash.h>
|
||||||
#ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library
|
#ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library
|
||||||
#include <../src/Hash.h>
|
#include <../src/Hash.h>
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(ESP32) || defined(LIBRETINY)
|
#ifndef DEFAULT_MAX_WS_CLIENTS
|
||||||
#define DEFAULT_MAX_WS_CLIENTS 8
|
#ifdef ESP32
|
||||||
#else
|
#define DEFAULT_MAX_WS_CLIENTS 8
|
||||||
#define DEFAULT_MAX_WS_CLIENTS 4
|
#else
|
||||||
|
#define DEFAULT_MAX_WS_CLIENTS 4
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
using AsyncWebSocketSharedBuffer = std::shared_ptr<std::vector<uint8_t>>;
|
||||||
|
|
||||||
class AsyncWebSocket;
|
class AsyncWebSocket;
|
||||||
class AsyncWebSocketResponse;
|
class AsyncWebSocketResponse;
|
||||||
class AsyncWebSocketClient;
|
class AsyncWebSocketClient;
|
||||||
@ -79,94 +90,73 @@ typedef struct {
|
|||||||
uint64_t index;
|
uint64_t index;
|
||||||
} AwsFrameInfo;
|
} AwsFrameInfo;
|
||||||
|
|
||||||
typedef enum { WS_DISCONNECTED, WS_CONNECTED, WS_DISCONNECTING } AwsClientStatus;
|
typedef enum { WS_DISCONNECTED,
|
||||||
typedef enum { WS_CONTINUATION, WS_TEXT, WS_BINARY, WS_DISCONNECT = 0x08, WS_PING, WS_PONG } AwsFrameType;
|
WS_CONNECTED,
|
||||||
typedef enum { WS_MSG_SENDING, WS_MSG_SENT, WS_MSG_ERROR } AwsMessageStatus;
|
WS_DISCONNECTING } AwsClientStatus;
|
||||||
typedef enum { WS_EVT_CONNECT, WS_EVT_DISCONNECT, WS_EVT_PONG, WS_EVT_ERROR, WS_EVT_DATA } AwsEventType;
|
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 {
|
class AsyncWebSocketMessageBuffer {
|
||||||
|
friend AsyncWebSocket;
|
||||||
|
friend AsyncWebSocketClient;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint8_t * _data;
|
AsyncWebSocketSharedBuffer _buffer;
|
||||||
size_t _len;
|
|
||||||
bool _lock;
|
|
||||||
uint32_t _count;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AsyncWebSocketMessageBuffer();
|
AsyncWebSocketMessageBuffer() {}
|
||||||
AsyncWebSocketMessageBuffer(size_t size);
|
explicit AsyncWebSocketMessageBuffer(size_t size);
|
||||||
AsyncWebSocketMessageBuffer(uint8_t * data, size_t size);
|
AsyncWebSocketMessageBuffer(const uint8_t* data, size_t size);
|
||||||
AsyncWebSocketMessageBuffer(const AsyncWebSocketMessageBuffer &);
|
//~AsyncWebSocketMessageBuffer();
|
||||||
AsyncWebSocketMessageBuffer(AsyncWebSocketMessageBuffer &&);
|
|
||||||
~AsyncWebSocketMessageBuffer();
|
|
||||||
void operator ++(int i) { (void)i; _count++; }
|
|
||||||
void operator --(int i) { (void)i; if (_count > 0) { _count--; } ; }
|
|
||||||
bool reserve(size_t size);
|
bool reserve(size_t size);
|
||||||
void lock() { _lock = true; }
|
uint8_t* get() { return _buffer->data(); }
|
||||||
void unlock() { _lock = false; }
|
size_t length() const { return _buffer->size(); }
|
||||||
uint8_t * get() { return _data; }
|
|
||||||
size_t length() { return _len; }
|
|
||||||
uint32_t count() { return _count; }
|
|
||||||
bool canDelete() { return (!_count && !_lock); }
|
|
||||||
|
|
||||||
friend AsyncWebSocket;
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class AsyncWebSocketMessage {
|
class AsyncWebSocketMessage {
|
||||||
protected:
|
private:
|
||||||
uint8_t _opcode;
|
AsyncWebSocketSharedBuffer _WSbuffer;
|
||||||
bool _mask;
|
uint8_t _opcode{WS_TEXT};
|
||||||
AwsMessageStatus _status;
|
bool _mask{false};
|
||||||
|
AwsMessageStatus _status{WS_MSG_ERROR};
|
||||||
|
size_t _sent{};
|
||||||
|
size_t _ack{};
|
||||||
|
size_t _acked{};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AsyncWebSocketMessage():_opcode(WS_TEXT),_mask(false),_status(WS_MSG_ERROR){}
|
AsyncWebSocketMessage(AsyncWebSocketSharedBuffer buffer, uint8_t opcode = WS_TEXT, bool mask = false);
|
||||||
virtual ~AsyncWebSocketMessage(){}
|
|
||||||
virtual void ack(size_t len __attribute__((unused)), uint32_t time __attribute__((unused))){}
|
|
||||||
virtual size_t send(AsyncClient *client __attribute__((unused))){ return 0; }
|
|
||||||
virtual bool finished(){ return _status != WS_MSG_SENDING; }
|
|
||||||
virtual bool betweenFrames() const { return false; }
|
|
||||||
};
|
|
||||||
|
|
||||||
class AsyncWebSocketBasicMessage: public AsyncWebSocketMessage {
|
bool finished() const { return _status != WS_MSG_SENDING; }
|
||||||
private:
|
bool betweenFrames() const { return _acked == _ack; }
|
||||||
size_t _len;
|
|
||||||
size_t _sent;
|
|
||||||
size_t _ack;
|
|
||||||
size_t _acked;
|
|
||||||
uint8_t * _data;
|
|
||||||
public:
|
|
||||||
AsyncWebSocketBasicMessage(const char * data, size_t len, uint8_t opcode=WS_TEXT, bool mask=false);
|
|
||||||
AsyncWebSocketBasicMessage(uint8_t opcode=WS_TEXT, bool mask=false);
|
|
||||||
virtual ~AsyncWebSocketBasicMessage() override;
|
|
||||||
virtual bool betweenFrames() const override { return _acked == _ack; }
|
|
||||||
virtual void ack(size_t len, uint32_t time) override ;
|
|
||||||
virtual size_t send(AsyncClient *client) override ;
|
|
||||||
};
|
|
||||||
|
|
||||||
class AsyncWebSocketMultiMessage: public AsyncWebSocketMessage {
|
void ack(size_t len, uint32_t time);
|
||||||
private:
|
size_t send(AsyncClient* client);
|
||||||
uint8_t * _data;
|
|
||||||
size_t _len;
|
|
||||||
size_t _sent;
|
|
||||||
size_t _ack;
|
|
||||||
size_t _acked;
|
|
||||||
AsyncWebSocketMessageBuffer * _WSbuffer;
|
|
||||||
public:
|
|
||||||
AsyncWebSocketMultiMessage(AsyncWebSocketMessageBuffer * buffer, uint8_t opcode=WS_TEXT, bool mask=false);
|
|
||||||
virtual ~AsyncWebSocketMultiMessage() override;
|
|
||||||
virtual bool betweenFrames() const override { return _acked == _ack; }
|
|
||||||
virtual void ack(size_t len, uint32_t time) override ;
|
|
||||||
virtual size_t send(AsyncClient *client) override ;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class AsyncWebSocketClient {
|
class AsyncWebSocketClient {
|
||||||
private:
|
private:
|
||||||
AsyncClient *_client;
|
AsyncClient* _client;
|
||||||
AsyncWebSocket *_server;
|
AsyncWebSocket* _server;
|
||||||
uint32_t _clientId;
|
uint32_t _clientId;
|
||||||
AwsClientStatus _status;
|
AwsClientStatus _status;
|
||||||
|
#ifdef ESP32
|
||||||
LinkedList<AsyncWebSocketControl *> _controlQueue;
|
mutable std::mutex _lock;
|
||||||
LinkedList<AsyncWebSocketMessage *> _messageQueue;
|
#endif
|
||||||
|
std::deque<AsyncWebSocketControl> _controlQueue;
|
||||||
|
std::deque<AsyncWebSocketMessage> _messageQueue;
|
||||||
|
bool closeWhenFull = true;
|
||||||
|
|
||||||
uint8_t _pstate;
|
uint8_t _pstate;
|
||||||
AwsFrameInfo _pinfo;
|
AwsFrameInfo _pinfo;
|
||||||
@ -174,181 +164,207 @@ class AsyncWebSocketClient {
|
|||||||
uint32_t _lastMessageTime;
|
uint32_t _lastMessageTime;
|
||||||
uint32_t _keepAlivePeriod;
|
uint32_t _keepAlivePeriod;
|
||||||
|
|
||||||
void _queueMessage(AsyncWebSocketMessage *dataMessage);
|
void _queueControl(uint8_t opcode, const uint8_t* data = NULL, size_t len = 0, bool mask = false);
|
||||||
void _queueControl(AsyncWebSocketControl *controlMessage);
|
void _queueMessage(AsyncWebSocketSharedBuffer buffer, uint8_t opcode = WS_TEXT, bool mask = false);
|
||||||
void _runQueue();
|
void _runQueue();
|
||||||
|
void _clearQueue();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void *_tempObject;
|
void* _tempObject;
|
||||||
|
|
||||||
AsyncWebSocketClient(AsyncWebServerRequest *request, AsyncWebSocket *server);
|
AsyncWebSocketClient(AsyncWebServerRequest* request, AsyncWebSocket* server);
|
||||||
~AsyncWebSocketClient();
|
~AsyncWebSocketClient();
|
||||||
|
|
||||||
//client id increments for the given server
|
// client id increments for the given server
|
||||||
uint32_t id(){ return _clientId; }
|
uint32_t id() const { return _clientId; }
|
||||||
AwsClientStatus status(){ return _status; }
|
AwsClientStatus status() const { return _status; }
|
||||||
AsyncClient* client(){ return _client; }
|
AsyncClient* client() { return _client; }
|
||||||
AsyncWebSocket *server(){ return _server; }
|
const AsyncClient* client() const { return _client; }
|
||||||
AwsFrameInfo const &pinfo() const { return _pinfo; }
|
AsyncWebSocket* server() { return _server; }
|
||||||
|
const AsyncWebSocket* server() const { return _server; }
|
||||||
|
AwsFrameInfo const& pinfo() const { return _pinfo; }
|
||||||
|
|
||||||
IPAddress remoteIP();
|
// - If "true" (default), the connection will be closed if the message queue is full.
|
||||||
uint16_t remotePort();
|
// This is the default behavior in yubox-node-org, which is not silently discarding messages but instead closes the connection.
|
||||||
|
// The big issue with this behavior is that is can cause the UI to automatically re-create a new WS connection, which can be filled again,
|
||||||
|
// and so on, causing a resource exhaustion.
|
||||||
|
//
|
||||||
|
// - If "false", the incoming message will be discarded if the queue is full.
|
||||||
|
// This is the default behavior in the original ESPAsyncWebServer library from me-no-dev.
|
||||||
|
// This behavior allows the best performance at the expense of unreliable message delivery in case the queue is full (some messages may be lost).
|
||||||
|
//
|
||||||
|
// - In any case, when the queue is full, a message is logged.
|
||||||
|
// - IT is recommended to use the methods queueIsFull(), availableForWriteAll(), availableForWrite(clientId) to check if the queue is full before sending a message.
|
||||||
|
//
|
||||||
|
// Usage:
|
||||||
|
// - can be set in the onEvent listener when connecting (event type is: WS_EVT_CONNECT)
|
||||||
|
//
|
||||||
|
// Use cases:,
|
||||||
|
// - if using websocket to send logging messages, maybe some loss is acceptable.
|
||||||
|
// - But if using websocket to send UI update messages, maybe the connection should be closed and the UI redrawn.
|
||||||
|
void setCloseClientOnQueueFull(bool close) { closeWhenFull = close; }
|
||||||
|
bool willCloseClientOnQueueFull() const { return closeWhenFull; }
|
||||||
|
|
||||||
//control frames
|
IPAddress remoteIP() const;
|
||||||
void close(uint16_t code=0, const char * message=NULL);
|
uint16_t remotePort() const;
|
||||||
void ping(uint8_t *data=NULL, size_t len=0);
|
|
||||||
|
|
||||||
//set auto-ping period in seconds. disabled if zero (default)
|
bool shouldBeDeleted() const { return !_client; }
|
||||||
void keepAlivePeriod(uint16_t seconds){
|
|
||||||
|
// 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;
|
_keepAlivePeriod = seconds * 1000;
|
||||||
}
|
}
|
||||||
uint16_t keepAlivePeriod(){
|
uint16_t keepAlivePeriod() {
|
||||||
return (uint16_t)(_keepAlivePeriod / 1000);
|
return (uint16_t)(_keepAlivePeriod / 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
//data packets
|
// data packets
|
||||||
void message(AsyncWebSocketMessage *message){ _queueMessage(message); }
|
void message(AsyncWebSocketSharedBuffer buffer, uint8_t opcode = WS_TEXT, bool mask = false) { _queueMessage(buffer, opcode, mask); }
|
||||||
bool queueIsFull();
|
bool queueIsFull() const;
|
||||||
|
size_t queueLen() const;
|
||||||
|
|
||||||
size_t printf(const char *format, ...) __attribute__ ((format (printf, 2, 3)));
|
size_t printf(const char* format, ...) __attribute__((format(printf, 2, 3)));
|
||||||
#ifndef ESP32
|
|
||||||
size_t printf_P(PGM_P formatP, ...) __attribute__ ((format (printf, 2, 3)));
|
|
||||||
#endif
|
|
||||||
void text(const char * message, size_t len);
|
|
||||||
void text(const char * message);
|
|
||||||
void text(uint8_t * message, size_t len);
|
|
||||||
void text(char * message);
|
|
||||||
void text(const String &message);
|
|
||||||
void text(const __FlashStringHelper *data);
|
|
||||||
void text(AsyncWebSocketMessageBuffer *buffer);
|
|
||||||
|
|
||||||
void binary(const char * message, size_t len);
|
void text(AsyncWebSocketSharedBuffer buffer);
|
||||||
void binary(const char * message);
|
void text(const uint8_t* message, size_t len);
|
||||||
void binary(uint8_t * message, size_t len);
|
void text(const char* message, size_t len);
|
||||||
void binary(char * message);
|
void text(const char* message);
|
||||||
void binary(const String &message);
|
void text(const String& message);
|
||||||
void binary(const __FlashStringHelper *data, size_t len);
|
void text(AsyncWebSocketMessageBuffer* buffer);
|
||||||
void binary(AsyncWebSocketMessageBuffer *buffer);
|
|
||||||
|
|
||||||
bool canSend() { return _messageQueue.length() < WS_MAX_QUEUED_MESSAGES; }
|
void binary(AsyncWebSocketSharedBuffer buffer);
|
||||||
|
void binary(const uint8_t* message, size_t len);
|
||||||
|
void binary(const char* message, size_t len);
|
||||||
|
void binary(const char* message);
|
||||||
|
void binary(const String& message);
|
||||||
|
void binary(AsyncWebSocketMessageBuffer* buffer);
|
||||||
|
|
||||||
//system callbacks (do not call)
|
bool canSend() const;
|
||||||
|
|
||||||
|
// system callbacks (do not call)
|
||||||
void _onAck(size_t len, uint32_t time);
|
void _onAck(size_t len, uint32_t time);
|
||||||
void _onError(int8_t);
|
void _onError(int8_t);
|
||||||
void _onPoll();
|
void _onPoll();
|
||||||
void _onTimeout(uint32_t time);
|
void _onTimeout(uint32_t time);
|
||||||
void _onDisconnect();
|
void _onDisconnect();
|
||||||
void _onData(void *pbuf, size_t plen);
|
void _onData(void* pbuf, size_t plen);
|
||||||
|
|
||||||
|
#ifdef ESP8266
|
||||||
|
size_t printf_P(PGM_P formatP, ...) __attribute__((format(printf, 2, 3)));
|
||||||
|
void text(const __FlashStringHelper* message);
|
||||||
|
void binary(const __FlashStringHelper* message, size_t len);
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef std::function<void(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len)> AwsEventHandler;
|
using AwsHandshakeHandler = std::function<bool(AsyncWebServerRequest* request)>;
|
||||||
|
using AwsEventHandler = std::function<void(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len)>;
|
||||||
|
|
||||||
//WebServer Handler implementation that plays the role of a socket server
|
// WebServer Handler implementation that plays the role of a socket server
|
||||||
class AsyncWebSocket: public AsyncWebHandler {
|
class AsyncWebSocket : public AsyncWebHandler {
|
||||||
public:
|
|
||||||
typedef LinkedList<AsyncWebSocketClient *> AsyncWebSocketClientLinkedList;
|
|
||||||
private:
|
private:
|
||||||
String _url;
|
String _url;
|
||||||
AsyncWebSocketClientLinkedList _clients;
|
std::list<AsyncWebSocketClient> _clients;
|
||||||
uint32_t _cNextId;
|
uint32_t _cNextId;
|
||||||
AwsEventHandler _eventHandler;
|
AwsEventHandler _eventHandler{nullptr};
|
||||||
|
AwsHandshakeHandler _handshakeHandler;
|
||||||
bool _enabled;
|
bool _enabled;
|
||||||
AsyncWebLock _lock;
|
#ifdef ESP32
|
||||||
|
mutable std::mutex _lock;
|
||||||
|
#endif
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AsyncWebSocket(const String& url);
|
explicit AsyncWebSocket(const char* url) : _url(url), _cNextId(1), _enabled(true) {}
|
||||||
~AsyncWebSocket();
|
AsyncWebSocket(const String& url) : _url(url), _cNextId(1), _enabled(true) {}
|
||||||
const char * url() const { return _url.c_str(); }
|
~AsyncWebSocket() {};
|
||||||
void enable(bool e){ _enabled = e; }
|
const char* url() const { return _url.c_str(); }
|
||||||
|
void enable(bool e) { _enabled = e; }
|
||||||
bool enabled() const { return _enabled; }
|
bool enabled() const { return _enabled; }
|
||||||
bool availableForWriteAll();
|
bool availableForWriteAll();
|
||||||
bool availableForWrite(uint32_t id);
|
bool availableForWrite(uint32_t id);
|
||||||
|
|
||||||
size_t count() const;
|
size_t count() const;
|
||||||
AsyncWebSocketClient * client(uint32_t id);
|
AsyncWebSocketClient* client(uint32_t id);
|
||||||
bool hasClient(uint32_t id){ return client(id) != NULL; }
|
bool hasClient(uint32_t id) { return client(id) != nullptr; }
|
||||||
|
|
||||||
void close(uint32_t id, uint16_t code=0, const char * message=NULL);
|
void close(uint32_t id, uint16_t code = 0, const char* message = NULL);
|
||||||
void closeAll(uint16_t code=0, const char * message=NULL);
|
void closeAll(uint16_t code = 0, const char* message = NULL);
|
||||||
void cleanupClients(uint16_t maxClients = DEFAULT_MAX_WS_CLIENTS);
|
void cleanupClients(uint16_t maxClients = DEFAULT_MAX_WS_CLIENTS);
|
||||||
|
|
||||||
void ping(uint32_t id, uint8_t *data=NULL, size_t len=0);
|
void ping(uint32_t id, const uint8_t* data = NULL, size_t len = 0);
|
||||||
void pingAll(uint8_t *data=NULL, size_t len=0); // done
|
void pingAll(const uint8_t* data = NULL, size_t len = 0); // done
|
||||||
|
|
||||||
void text(uint32_t id, const char * message, size_t len);
|
void text(uint32_t id, const uint8_t* message, size_t len);
|
||||||
void text(uint32_t id, const char * message);
|
void text(uint32_t id, const char* message, size_t len);
|
||||||
void text(uint32_t id, uint8_t * message, size_t len);
|
void text(uint32_t id, const char* message);
|
||||||
void text(uint32_t id, char * message);
|
void text(uint32_t id, const String& message);
|
||||||
void text(uint32_t id, const String &message);
|
void text(uint32_t id, AsyncWebSocketMessageBuffer* buffer);
|
||||||
void text(uint32_t id, const __FlashStringHelper *message);
|
void text(uint32_t id, AsyncWebSocketSharedBuffer buffer);
|
||||||
|
|
||||||
void textAll(const char * message, size_t len);
|
void textAll(const uint8_t* message, size_t len);
|
||||||
void textAll(const char * message);
|
void textAll(const char* message, size_t len);
|
||||||
void textAll(uint8_t * message, size_t len);
|
void textAll(const char* message);
|
||||||
void textAll(char * message);
|
void textAll(const String& message);
|
||||||
void textAll(const String &message);
|
void textAll(AsyncWebSocketMessageBuffer* buffer);
|
||||||
void textAll(const __FlashStringHelper *message); // need to convert
|
void textAll(AsyncWebSocketSharedBuffer buffer);
|
||||||
void textAll(AsyncWebSocketMessageBuffer * buffer);
|
|
||||||
|
|
||||||
void binary(uint32_t id, const char * message, size_t len);
|
void binary(uint32_t id, const uint8_t* message, size_t len);
|
||||||
void binary(uint32_t id, const char * message);
|
void binary(uint32_t id, const char* message, size_t len);
|
||||||
void binary(uint32_t id, uint8_t * message, size_t len);
|
void binary(uint32_t id, const char* message);
|
||||||
void binary(uint32_t id, char * message);
|
void binary(uint32_t id, const String& message);
|
||||||
void binary(uint32_t id, const String &message);
|
void binary(uint32_t id, AsyncWebSocketMessageBuffer* buffer);
|
||||||
void binary(uint32_t id, const __FlashStringHelper *message, size_t len);
|
void binary(uint32_t id, AsyncWebSocketSharedBuffer buffer);
|
||||||
|
|
||||||
void binaryAll(const char * message, size_t len);
|
void binaryAll(const uint8_t* message, size_t len);
|
||||||
void binaryAll(const char * message);
|
void binaryAll(const char* message, size_t len);
|
||||||
void binaryAll(uint8_t * message, size_t len);
|
void binaryAll(const char* message);
|
||||||
void binaryAll(char * message);
|
void binaryAll(const String& message);
|
||||||
void binaryAll(const String &message);
|
void binaryAll(AsyncWebSocketMessageBuffer* buffer);
|
||||||
void binaryAll(const __FlashStringHelper *message, size_t len);
|
void binaryAll(AsyncWebSocketSharedBuffer buffer);
|
||||||
void binaryAll(AsyncWebSocketMessageBuffer * buffer);
|
|
||||||
|
|
||||||
void message(uint32_t id, AsyncWebSocketMessage *message);
|
size_t printf(uint32_t id, const char* format, ...) __attribute__((format(printf, 3, 4)));
|
||||||
void messageAll(AsyncWebSocketMultiMessage *message);
|
size_t printfAll(const char* format, ...) __attribute__((format(printf, 2, 3)));
|
||||||
|
|
||||||
size_t printf(uint32_t id, const char *format, ...) __attribute__ ((format (printf, 3, 4)));
|
#ifdef ESP8266
|
||||||
size_t printfAll(const char *format, ...) __attribute__ ((format (printf, 2, 3)));
|
void text(uint32_t id, const __FlashStringHelper* message);
|
||||||
#ifndef ESP32
|
void textAll(const __FlashStringHelper* message);
|
||||||
size_t printf_P(uint32_t id, PGM_P formatP, ...) __attribute__ ((format (printf, 3, 4)));
|
void binary(uint32_t id, const __FlashStringHelper* message, size_t len);
|
||||||
|
void binaryAll(const __FlashStringHelper* message, size_t len);
|
||||||
|
size_t printf_P(uint32_t id, PGM_P formatP, ...) __attribute__((format(printf, 3, 4)));
|
||||||
|
size_t printfAll_P(PGM_P formatP, ...) __attribute__((format(printf, 2, 3)));
|
||||||
#endif
|
#endif
|
||||||
size_t printfAll_P(PGM_P formatP, ...) __attribute__ ((format (printf, 2, 3)));
|
|
||||||
|
|
||||||
//event listener
|
void onEvent(AwsEventHandler handler) { _eventHandler = handler; }
|
||||||
void onEvent(AwsEventHandler handler){
|
void handleHandshake(AwsHandshakeHandler handler) { _handshakeHandler = handler; }
|
||||||
_eventHandler = handler;
|
|
||||||
}
|
|
||||||
|
|
||||||
//system callbacks (do not call)
|
|
||||||
uint32_t _getNextId(){ return _cNextId++; }
|
|
||||||
void _addClient(AsyncWebSocketClient * client);
|
|
||||||
void _handleDisconnect(AsyncWebSocketClient * client);
|
|
||||||
void _handleEvent(AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len);
|
|
||||||
virtual bool canHandle(AsyncWebServerRequest *request) override final;
|
|
||||||
virtual void handleRequest(AsyncWebServerRequest *request) override final;
|
|
||||||
|
|
||||||
|
// system callbacks (do not call)
|
||||||
|
uint32_t _getNextId() { return _cNextId++; }
|
||||||
|
AsyncWebSocketClient* _newClient(AsyncWebServerRequest* request);
|
||||||
|
void _handleEvent(AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len);
|
||||||
|
virtual bool canHandle(AsyncWebServerRequest* request) override final;
|
||||||
|
virtual void handleRequest(AsyncWebServerRequest* request) override final;
|
||||||
|
|
||||||
// messagebuffer functions/objects.
|
// messagebuffer functions/objects.
|
||||||
AsyncWebSocketMessageBuffer * makeBuffer(size_t size = 0);
|
AsyncWebSocketMessageBuffer* makeBuffer(size_t size = 0);
|
||||||
AsyncWebSocketMessageBuffer * makeBuffer(uint8_t * data, size_t size);
|
AsyncWebSocketMessageBuffer* makeBuffer(const uint8_t* data, size_t size);
|
||||||
LinkedList<AsyncWebSocketMessageBuffer *> _buffers;
|
|
||||||
void _cleanBuffers();
|
|
||||||
|
|
||||||
AsyncWebSocketClientLinkedList getClients() const;
|
std::list<AsyncWebSocketClient>& getClients() { return _clients; }
|
||||||
};
|
};
|
||||||
|
|
||||||
//WebServer response to authenticate the socket and detach the tcp client from the web server request
|
// WebServer response to authenticate the socket and detach the tcp client from the web server request
|
||||||
class AsyncWebSocketResponse: public AsyncWebServerResponse {
|
class AsyncWebSocketResponse : public AsyncWebServerResponse {
|
||||||
private:
|
private:
|
||||||
String _content;
|
String _content;
|
||||||
AsyncWebSocket *_server;
|
AsyncWebSocket* _server;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AsyncWebSocketResponse(const String& key, AsyncWebSocket *server);
|
AsyncWebSocketResponse(const String& key, AsyncWebSocket* server);
|
||||||
void _respond(AsyncWebServerRequest *request);
|
void _respond(AsyncWebServerRequest* request);
|
||||||
size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time);
|
size_t _ack(AsyncWebServerRequest* request, size_t len, uint32_t time);
|
||||||
bool _sourceValid() const { return true; }
|
bool _sourceValid() const { return true; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
#endif /* ASYNCWEBSOCKET_H_ */
|
#endif /* ASYNCWEBSOCKET_H_ */
|
||||||
|
@ -1,87 +0,0 @@
|
|||||||
#ifndef ASYNCWEBSYNCHRONIZATION_H_
|
|
||||||
#define ASYNCWEBSYNCHRONIZATION_H_
|
|
||||||
|
|
||||||
// Synchronisation is only available on ESP32, as the ESP8266 isn't using FreeRTOS by default
|
|
||||||
|
|
||||||
#include <ESPAsyncWebServer.h>
|
|
||||||
|
|
||||||
#if defined(ESP32) || (defined(LIBRETINY) && LT_HAS_FREERTOS)
|
|
||||||
|
|
||||||
// This is the ESP32 version of the Sync Lock, using the FreeRTOS Semaphore
|
|
||||||
class AsyncWebLock
|
|
||||||
{
|
|
||||||
private:
|
|
||||||
SemaphoreHandle_t _lock;
|
|
||||||
mutable void *_lockedBy;
|
|
||||||
|
|
||||||
public:
|
|
||||||
AsyncWebLock() {
|
|
||||||
_lock = xSemaphoreCreateBinary();
|
|
||||||
_lockedBy = NULL;
|
|
||||||
xSemaphoreGive(_lock);
|
|
||||||
}
|
|
||||||
|
|
||||||
~AsyncWebLock() {
|
|
||||||
vSemaphoreDelete(_lock);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool lock() const {
|
|
||||||
extern void *pxCurrentTCB;
|
|
||||||
if (_lockedBy != pxCurrentTCB) {
|
|
||||||
xSemaphoreTake(_lock, portMAX_DELAY);
|
|
||||||
_lockedBy = pxCurrentTCB;
|
|
||||||
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 {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
#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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // ASYNCWEBSYNCHRONIZATION_H_
|
|
284
src/BackPort_SHA1Builder.cpp
Normal file
284
src/BackPort_SHA1Builder.cpp
Normal file
@ -0,0 +1,284 @@
|
|||||||
|
/*
|
||||||
|
* FIPS-180-1 compliant SHA-1 implementation
|
||||||
|
*
|
||||||
|
* Copyright (C) 2006-2015, ARM Limited, All Rights Reserved
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
* not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
* This file is part of mbed TLS (https://tls.mbed.org)
|
||||||
|
* Modified for esp32 by Lucas Saavedra Vaz on 11 Jan 2024
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#if ESP_IDF_VERSION_MAJOR < 5
|
||||||
|
|
||||||
|
#include "BackPort_SHA1Builder.h"
|
||||||
|
|
||||||
|
// 32-bit integer manipulation macros (big endian)
|
||||||
|
|
||||||
|
#ifndef GET_UINT32_BE
|
||||||
|
#define GET_UINT32_BE(n, b, i) \
|
||||||
|
{ (n) = ((uint32_t)(b)[(i)] << 24) | ((uint32_t)(b)[(i) + 1] << 16) | ((uint32_t)(b)[(i) + 2] << 8) | ((uint32_t)(b)[(i) + 3]); }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef PUT_UINT32_BE
|
||||||
|
#define PUT_UINT32_BE(n, b, i) \
|
||||||
|
{ \
|
||||||
|
(b)[(i)] = (uint8_t)((n) >> 24); \
|
||||||
|
(b)[(i) + 1] = (uint8_t)((n) >> 16); \
|
||||||
|
(b)[(i) + 2] = (uint8_t)((n) >> 8); \
|
||||||
|
(b)[(i) + 3] = (uint8_t)((n)); \
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
|
||||||
|
static const uint8_t sha1_padding[64] = {0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||||
|
|
||||||
|
// Private methods
|
||||||
|
|
||||||
|
void SHA1Builder::process(const uint8_t *data) {
|
||||||
|
uint32_t temp, W[16], A, B, C, D, E;
|
||||||
|
|
||||||
|
GET_UINT32_BE(W[0], data, 0);
|
||||||
|
GET_UINT32_BE(W[1], data, 4);
|
||||||
|
GET_UINT32_BE(W[2], data, 8);
|
||||||
|
GET_UINT32_BE(W[3], data, 12);
|
||||||
|
GET_UINT32_BE(W[4], data, 16);
|
||||||
|
GET_UINT32_BE(W[5], data, 20);
|
||||||
|
GET_UINT32_BE(W[6], data, 24);
|
||||||
|
GET_UINT32_BE(W[7], data, 28);
|
||||||
|
GET_UINT32_BE(W[8], data, 32);
|
||||||
|
GET_UINT32_BE(W[9], data, 36);
|
||||||
|
GET_UINT32_BE(W[10], data, 40);
|
||||||
|
GET_UINT32_BE(W[11], data, 44);
|
||||||
|
GET_UINT32_BE(W[12], data, 48);
|
||||||
|
GET_UINT32_BE(W[13], data, 52);
|
||||||
|
GET_UINT32_BE(W[14], data, 56);
|
||||||
|
GET_UINT32_BE(W[15], data, 60);
|
||||||
|
|
||||||
|
#define sha1_S(x, n) ((x << n) | ((x & 0xFFFFFFFF) >> (32 - n)))
|
||||||
|
|
||||||
|
#define sha1_R(t) (temp = W[(t - 3) & 0x0F] ^ W[(t - 8) & 0x0F] ^ W[(t - 14) & 0x0F] ^ W[t & 0x0F], (W[t & 0x0F] = sha1_S(temp, 1)))
|
||||||
|
|
||||||
|
#define sha1_P(a, b, c, d, e, x) \
|
||||||
|
{ \
|
||||||
|
e += sha1_S(a, 5) + sha1_F(b, c, d) + sha1_K + x; \
|
||||||
|
b = sha1_S(b, 30); \
|
||||||
|
}
|
||||||
|
|
||||||
|
A = state[0];
|
||||||
|
B = state[1];
|
||||||
|
C = state[2];
|
||||||
|
D = state[3];
|
||||||
|
E = state[4];
|
||||||
|
|
||||||
|
#define sha1_F(x, y, z) (z ^ (x & (y ^ z)))
|
||||||
|
#define sha1_K 0x5A827999
|
||||||
|
|
||||||
|
sha1_P(A, B, C, D, E, W[0]);
|
||||||
|
sha1_P(E, A, B, C, D, W[1]);
|
||||||
|
sha1_P(D, E, A, B, C, W[2]);
|
||||||
|
sha1_P(C, D, E, A, B, W[3]);
|
||||||
|
sha1_P(B, C, D, E, A, W[4]);
|
||||||
|
sha1_P(A, B, C, D, E, W[5]);
|
||||||
|
sha1_P(E, A, B, C, D, W[6]);
|
||||||
|
sha1_P(D, E, A, B, C, W[7]);
|
||||||
|
sha1_P(C, D, E, A, B, W[8]);
|
||||||
|
sha1_P(B, C, D, E, A, W[9]);
|
||||||
|
sha1_P(A, B, C, D, E, W[10]);
|
||||||
|
sha1_P(E, A, B, C, D, W[11]);
|
||||||
|
sha1_P(D, E, A, B, C, W[12]);
|
||||||
|
sha1_P(C, D, E, A, B, W[13]);
|
||||||
|
sha1_P(B, C, D, E, A, W[14]);
|
||||||
|
sha1_P(A, B, C, D, E, W[15]);
|
||||||
|
sha1_P(E, A, B, C, D, sha1_R(16));
|
||||||
|
sha1_P(D, E, A, B, C, sha1_R(17));
|
||||||
|
sha1_P(C, D, E, A, B, sha1_R(18));
|
||||||
|
sha1_P(B, C, D, E, A, sha1_R(19));
|
||||||
|
|
||||||
|
#undef sha1_K
|
||||||
|
#undef sha1_F
|
||||||
|
|
||||||
|
#define sha1_F(x, y, z) (x ^ y ^ z)
|
||||||
|
#define sha1_K 0x6ED9EBA1
|
||||||
|
|
||||||
|
sha1_P(A, B, C, D, E, sha1_R(20));
|
||||||
|
sha1_P(E, A, B, C, D, sha1_R(21));
|
||||||
|
sha1_P(D, E, A, B, C, sha1_R(22));
|
||||||
|
sha1_P(C, D, E, A, B, sha1_R(23));
|
||||||
|
sha1_P(B, C, D, E, A, sha1_R(24));
|
||||||
|
sha1_P(A, B, C, D, E, sha1_R(25));
|
||||||
|
sha1_P(E, A, B, C, D, sha1_R(26));
|
||||||
|
sha1_P(D, E, A, B, C, sha1_R(27));
|
||||||
|
sha1_P(C, D, E, A, B, sha1_R(28));
|
||||||
|
sha1_P(B, C, D, E, A, sha1_R(29));
|
||||||
|
sha1_P(A, B, C, D, E, sha1_R(30));
|
||||||
|
sha1_P(E, A, B, C, D, sha1_R(31));
|
||||||
|
sha1_P(D, E, A, B, C, sha1_R(32));
|
||||||
|
sha1_P(C, D, E, A, B, sha1_R(33));
|
||||||
|
sha1_P(B, C, D, E, A, sha1_R(34));
|
||||||
|
sha1_P(A, B, C, D, E, sha1_R(35));
|
||||||
|
sha1_P(E, A, B, C, D, sha1_R(36));
|
||||||
|
sha1_P(D, E, A, B, C, sha1_R(37));
|
||||||
|
sha1_P(C, D, E, A, B, sha1_R(38));
|
||||||
|
sha1_P(B, C, D, E, A, sha1_R(39));
|
||||||
|
|
||||||
|
#undef sha1_K
|
||||||
|
#undef sha1_F
|
||||||
|
|
||||||
|
#define sha1_F(x, y, z) ((x & y) | (z & (x | y)))
|
||||||
|
#define sha1_K 0x8F1BBCDC
|
||||||
|
|
||||||
|
sha1_P(A, B, C, D, E, sha1_R(40));
|
||||||
|
sha1_P(E, A, B, C, D, sha1_R(41));
|
||||||
|
sha1_P(D, E, A, B, C, sha1_R(42));
|
||||||
|
sha1_P(C, D, E, A, B, sha1_R(43));
|
||||||
|
sha1_P(B, C, D, E, A, sha1_R(44));
|
||||||
|
sha1_P(A, B, C, D, E, sha1_R(45));
|
||||||
|
sha1_P(E, A, B, C, D, sha1_R(46));
|
||||||
|
sha1_P(D, E, A, B, C, sha1_R(47));
|
||||||
|
sha1_P(C, D, E, A, B, sha1_R(48));
|
||||||
|
sha1_P(B, C, D, E, A, sha1_R(49));
|
||||||
|
sha1_P(A, B, C, D, E, sha1_R(50));
|
||||||
|
sha1_P(E, A, B, C, D, sha1_R(51));
|
||||||
|
sha1_P(D, E, A, B, C, sha1_R(52));
|
||||||
|
sha1_P(C, D, E, A, B, sha1_R(53));
|
||||||
|
sha1_P(B, C, D, E, A, sha1_R(54));
|
||||||
|
sha1_P(A, B, C, D, E, sha1_R(55));
|
||||||
|
sha1_P(E, A, B, C, D, sha1_R(56));
|
||||||
|
sha1_P(D, E, A, B, C, sha1_R(57));
|
||||||
|
sha1_P(C, D, E, A, B, sha1_R(58));
|
||||||
|
sha1_P(B, C, D, E, A, sha1_R(59));
|
||||||
|
|
||||||
|
#undef sha1_K
|
||||||
|
#undef sha1_F
|
||||||
|
|
||||||
|
#define sha1_F(x, y, z) (x ^ y ^ z)
|
||||||
|
#define sha1_K 0xCA62C1D6
|
||||||
|
|
||||||
|
sha1_P(A, B, C, D, E, sha1_R(60));
|
||||||
|
sha1_P(E, A, B, C, D, sha1_R(61));
|
||||||
|
sha1_P(D, E, A, B, C, sha1_R(62));
|
||||||
|
sha1_P(C, D, E, A, B, sha1_R(63));
|
||||||
|
sha1_P(B, C, D, E, A, sha1_R(64));
|
||||||
|
sha1_P(A, B, C, D, E, sha1_R(65));
|
||||||
|
sha1_P(E, A, B, C, D, sha1_R(66));
|
||||||
|
sha1_P(D, E, A, B, C, sha1_R(67));
|
||||||
|
sha1_P(C, D, E, A, B, sha1_R(68));
|
||||||
|
sha1_P(B, C, D, E, A, sha1_R(69));
|
||||||
|
sha1_P(A, B, C, D, E, sha1_R(70));
|
||||||
|
sha1_P(E, A, B, C, D, sha1_R(71));
|
||||||
|
sha1_P(D, E, A, B, C, sha1_R(72));
|
||||||
|
sha1_P(C, D, E, A, B, sha1_R(73));
|
||||||
|
sha1_P(B, C, D, E, A, sha1_R(74));
|
||||||
|
sha1_P(A, B, C, D, E, sha1_R(75));
|
||||||
|
sha1_P(E, A, B, C, D, sha1_R(76));
|
||||||
|
sha1_P(D, E, A, B, C, sha1_R(77));
|
||||||
|
sha1_P(C, D, E, A, B, sha1_R(78));
|
||||||
|
sha1_P(B, C, D, E, A, sha1_R(79));
|
||||||
|
|
||||||
|
#undef sha1_K
|
||||||
|
#undef sha1_F
|
||||||
|
|
||||||
|
state[0] += A;
|
||||||
|
state[1] += B;
|
||||||
|
state[2] += C;
|
||||||
|
state[3] += D;
|
||||||
|
state[4] += E;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Public methods
|
||||||
|
|
||||||
|
void SHA1Builder::begin() {
|
||||||
|
total[0] = 0;
|
||||||
|
total[1] = 0;
|
||||||
|
|
||||||
|
state[0] = 0x67452301;
|
||||||
|
state[1] = 0xEFCDAB89;
|
||||||
|
state[2] = 0x98BADCFE;
|
||||||
|
state[3] = 0x10325476;
|
||||||
|
state[4] = 0xC3D2E1F0;
|
||||||
|
|
||||||
|
memset(buffer, 0x00, sizeof(buffer));
|
||||||
|
memset(hash, 0x00, sizeof(hash));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SHA1Builder::add(const uint8_t *data, size_t len) {
|
||||||
|
size_t fill;
|
||||||
|
uint32_t left;
|
||||||
|
|
||||||
|
if (len == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
left = total[0] & 0x3F;
|
||||||
|
fill = 64 - left;
|
||||||
|
|
||||||
|
total[0] += (uint32_t)len;
|
||||||
|
total[0] &= 0xFFFFFFFF;
|
||||||
|
|
||||||
|
if (total[0] < (uint32_t)len) {
|
||||||
|
total[1]++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (left && len >= fill) {
|
||||||
|
memcpy((void *)(buffer + left), data, fill);
|
||||||
|
process(buffer);
|
||||||
|
data += fill;
|
||||||
|
len -= fill;
|
||||||
|
left = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (len >= 64) {
|
||||||
|
process(data);
|
||||||
|
data += 64;
|
||||||
|
len -= 64;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len > 0) {
|
||||||
|
memcpy((void *)(buffer + left), data, len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SHA1Builder::calculate(void) {
|
||||||
|
uint32_t last, padn;
|
||||||
|
uint32_t high, low;
|
||||||
|
uint8_t msglen[8];
|
||||||
|
|
||||||
|
high = (total[0] >> 29) | (total[1] << 3);
|
||||||
|
low = (total[0] << 3);
|
||||||
|
|
||||||
|
PUT_UINT32_BE(high, msglen, 0);
|
||||||
|
PUT_UINT32_BE(low, msglen, 4);
|
||||||
|
|
||||||
|
last = total[0] & 0x3F;
|
||||||
|
padn = (last < 56) ? (56 - last) : (120 - last);
|
||||||
|
|
||||||
|
add((uint8_t *)sha1_padding, padn);
|
||||||
|
add(msglen, 8);
|
||||||
|
|
||||||
|
PUT_UINT32_BE(state[0], hash, 0);
|
||||||
|
PUT_UINT32_BE(state[1], hash, 4);
|
||||||
|
PUT_UINT32_BE(state[2], hash, 8);
|
||||||
|
PUT_UINT32_BE(state[3], hash, 12);
|
||||||
|
PUT_UINT32_BE(state[4], hash, 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SHA1Builder::getBytes(uint8_t *output) {
|
||||||
|
memcpy(output, hash, SHA1_HASH_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // ESP_IDF_VERSION_MAJOR < 5
|
44
src/BackPort_SHA1Builder.h
Normal file
44
src/BackPort_SHA1Builder.h
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
// Copyright 2024 Espressif Systems (Shanghai) PTE LTD
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#if ESP_IDF_VERSION_MAJOR < 5
|
||||||
|
|
||||||
|
#ifndef SHA1Builder_h
|
||||||
|
#define SHA1Builder_h
|
||||||
|
|
||||||
|
#include <Stream.h>
|
||||||
|
#include <WString.h>
|
||||||
|
|
||||||
|
#define SHA1_HASH_SIZE 20
|
||||||
|
|
||||||
|
class SHA1Builder {
|
||||||
|
private:
|
||||||
|
uint32_t total[2]; /* number of bytes processed */
|
||||||
|
uint32_t state[5]; /* intermediate digest state */
|
||||||
|
unsigned char buffer[64]; /* data block being processed */
|
||||||
|
uint8_t hash[SHA1_HASH_SIZE]; /* SHA-1 result */
|
||||||
|
|
||||||
|
void process(const uint8_t* data);
|
||||||
|
|
||||||
|
public:
|
||||||
|
void begin();
|
||||||
|
void add(const uint8_t* data, size_t len);
|
||||||
|
void calculate();
|
||||||
|
void getBytes(uint8_t* output);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // SHA1Builder_h
|
||||||
|
|
||||||
|
#endif // ESP_IDF_VERSION_MAJOR < 5
|
16
src/ChunkPrint.cpp
Normal file
16
src/ChunkPrint.cpp
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
#include <ChunkPrint.h>
|
||||||
|
|
||||||
|
ChunkPrint::ChunkPrint(uint8_t* destination, size_t from, size_t len)
|
||||||
|
: _destination(destination), _to_skip(from), _to_write(len), _pos{0} {}
|
||||||
|
|
||||||
|
size_t ChunkPrint::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;
|
||||||
|
}
|
19
src/ChunkPrint.h
Normal file
19
src/ChunkPrint.h
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
#ifndef CHUNKPRINT_H
|
||||||
|
#define CHUNKPRINT_H
|
||||||
|
|
||||||
|
#include <Print.h>
|
||||||
|
|
||||||
|
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);
|
||||||
|
virtual ~ChunkPrint() {}
|
||||||
|
size_t write(uint8_t c);
|
||||||
|
size_t write(const uint8_t* buffer, size_t size) { return this->Print::write(buffer, size); }
|
||||||
|
};
|
||||||
|
#endif
|
File diff suppressed because it is too large
Load Diff
253
src/Middleware.cpp
Normal file
253
src/Middleware.cpp
Normal file
@ -0,0 +1,253 @@
|
|||||||
|
#include "WebAuthentication.h"
|
||||||
|
#include <ESPAsyncWebServer.h>
|
||||||
|
|
||||||
|
AsyncMiddlewareChain::~AsyncMiddlewareChain() {
|
||||||
|
for (AsyncMiddleware* m : _middlewares)
|
||||||
|
if (m->_freeOnRemoval)
|
||||||
|
delete m;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncMiddlewareChain::addMiddleware(ArMiddlewareCallback fn) {
|
||||||
|
AsyncMiddlewareFunction* m = new AsyncMiddlewareFunction(fn);
|
||||||
|
m->_freeOnRemoval = true;
|
||||||
|
_middlewares.emplace_back(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncMiddlewareChain::addMiddleware(AsyncMiddleware* middleware) {
|
||||||
|
if (middleware)
|
||||||
|
_middlewares.emplace_back(middleware);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncMiddlewareChain::addMiddlewares(std::vector<AsyncMiddleware*> middlewares) {
|
||||||
|
for (AsyncMiddleware* m : middlewares)
|
||||||
|
addMiddleware(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AsyncMiddlewareChain::removeMiddleware(AsyncMiddleware* middleware) {
|
||||||
|
// remove all middlewares from _middlewares vector being equal to middleware, delete them having _freeOnRemoval flag to true and resize the vector.
|
||||||
|
const size_t size = _middlewares.size();
|
||||||
|
_middlewares.erase(std::remove_if(_middlewares.begin(), _middlewares.end(), [middleware](AsyncMiddleware* m) {
|
||||||
|
if (m == middleware) {
|
||||||
|
if (m->_freeOnRemoval)
|
||||||
|
delete m;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}),
|
||||||
|
_middlewares.end());
|
||||||
|
return size != _middlewares.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncMiddlewareChain::_runChain(AsyncWebServerRequest* request, ArMiddlewareNext finalizer) {
|
||||||
|
if (!_middlewares.size())
|
||||||
|
return finalizer();
|
||||||
|
ArMiddlewareNext next;
|
||||||
|
std::list<AsyncMiddleware*>::iterator it = _middlewares.begin();
|
||||||
|
next = [this, &next, &it, request, finalizer]() {
|
||||||
|
if (it == _middlewares.end())
|
||||||
|
return finalizer();
|
||||||
|
AsyncMiddleware* m = *it;
|
||||||
|
it++;
|
||||||
|
return m->run(request, next);
|
||||||
|
};
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AuthenticationMiddleware::setUsername(const char* username) {
|
||||||
|
_username = username;
|
||||||
|
_hasCreds = _username.length() && _credentials.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AuthenticationMiddleware::setPassword(const char* password) {
|
||||||
|
_credentials = password;
|
||||||
|
_hash = false;
|
||||||
|
_hasCreds = _username.length() && _credentials.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AuthenticationMiddleware::setPasswordHash(const char* hash) {
|
||||||
|
_credentials = hash;
|
||||||
|
_hash = true;
|
||||||
|
_hasCreds = _username.length() && _credentials.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AuthenticationMiddleware::generateHash() {
|
||||||
|
// ensure we have all the necessary data
|
||||||
|
if (!_hasCreds)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// if we already have a hash, do nothing
|
||||||
|
if (_hash)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
switch (_authMethod) {
|
||||||
|
case AsyncAuthType::AUTH_DIGEST:
|
||||||
|
_credentials = generateDigestHash(_username.c_str(), _credentials.c_str(), _realm.c_str());
|
||||||
|
_hash = true;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case AsyncAuthType::AUTH_BASIC:
|
||||||
|
_credentials = generateBasicHash(_username.c_str(), _credentials.c_str());
|
||||||
|
_hash = true;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AuthenticationMiddleware::allowed(AsyncWebServerRequest* request) {
|
||||||
|
if (_authMethod == AsyncAuthType::AUTH_NONE)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (!_hasCreds)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return request->authenticate(_username.c_str(), _credentials.c_str(), _realm.c_str(), _hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AuthenticationMiddleware::run(AsyncWebServerRequest* request, ArMiddlewareNext next) {
|
||||||
|
return allowed(request) ? next() : request->requestAuthentication(_authMethod, _realm.c_str(), _authFailMsg.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void HeaderFreeMiddleware::run(AsyncWebServerRequest* request, ArMiddlewareNext next) {
|
||||||
|
std::vector<const char*> reqHeaders;
|
||||||
|
request->getHeaderNames(reqHeaders);
|
||||||
|
for (const char* h : reqHeaders) {
|
||||||
|
bool keep = false;
|
||||||
|
for (const char* k : _toKeep) {
|
||||||
|
if (strcasecmp(h, k) == 0) {
|
||||||
|
keep = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!keep) {
|
||||||
|
request->removeHeader(h);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HeaderFilterMiddleware::run(AsyncWebServerRequest* request, ArMiddlewareNext next) {
|
||||||
|
for (auto it = _toRemove.begin(); it != _toRemove.end(); ++it)
|
||||||
|
request->removeHeader(*it);
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoggingMiddleware::run(AsyncWebServerRequest* request, ArMiddlewareNext next) {
|
||||||
|
if (!isEnabled()) {
|
||||||
|
next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_out->print(F("* Connection from "));
|
||||||
|
_out->print(request->client()->remoteIP().toString());
|
||||||
|
_out->print(':');
|
||||||
|
_out->println(request->client()->remotePort());
|
||||||
|
_out->print('>');
|
||||||
|
_out->print(' ');
|
||||||
|
_out->print(request->methodToString());
|
||||||
|
_out->print(' ');
|
||||||
|
_out->print(request->url().c_str());
|
||||||
|
_out->print(F(" HTTP/1."));
|
||||||
|
_out->println(request->version());
|
||||||
|
for (auto& h : request->getHeaders()) {
|
||||||
|
if (h.value().length()) {
|
||||||
|
_out->print('>');
|
||||||
|
_out->print(' ');
|
||||||
|
_out->print(h.name());
|
||||||
|
_out->print(':');
|
||||||
|
_out->print(' ');
|
||||||
|
_out->println(h.value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_out->println(F(">"));
|
||||||
|
uint32_t elapsed = millis();
|
||||||
|
next();
|
||||||
|
elapsed = millis() - elapsed;
|
||||||
|
AsyncWebServerResponse* response = request->getResponse();
|
||||||
|
if (response) {
|
||||||
|
_out->print(F("* Processed in "));
|
||||||
|
_out->print(elapsed);
|
||||||
|
_out->println(F(" ms"));
|
||||||
|
_out->print('<');
|
||||||
|
_out->print(F(" HTTP/1."));
|
||||||
|
_out->print(request->version());
|
||||||
|
_out->print(' ');
|
||||||
|
_out->print(response->code());
|
||||||
|
_out->print(' ');
|
||||||
|
_out->println(AsyncWebServerResponse::responseCodeToString(response->code()));
|
||||||
|
for (auto& h : response->getHeaders()) {
|
||||||
|
if (h.value().length()) {
|
||||||
|
_out->print('<');
|
||||||
|
_out->print(' ');
|
||||||
|
_out->print(h.name());
|
||||||
|
_out->print(':');
|
||||||
|
_out->print(' ');
|
||||||
|
_out->println(h.value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_out->println('<');
|
||||||
|
} else {
|
||||||
|
_out->println(F("* Connection closed!"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CorsMiddleware::addCORSHeaders(AsyncWebServerResponse* response) {
|
||||||
|
response->addHeader(F("Access-Control-Allow-Origin"), _origin.c_str());
|
||||||
|
response->addHeader(F("Access-Control-Allow-Methods"), _methods.c_str());
|
||||||
|
response->addHeader(F("Access-Control-Allow-Headers"), _headers.c_str());
|
||||||
|
response->addHeader(F("Access-Control-Allow-Credentials"), _credentials ? F("true") : F("false"));
|
||||||
|
response->addHeader(F("Access-Control-Max-Age"), String(_maxAge).c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void CorsMiddleware::run(AsyncWebServerRequest* request, ArMiddlewareNext next) {
|
||||||
|
// Origin header ? => CORS handling
|
||||||
|
if (request->hasHeader(F("Origin"))) {
|
||||||
|
// check if this is a preflight request => handle it and return
|
||||||
|
if (request->method() == HTTP_OPTIONS) {
|
||||||
|
AsyncWebServerResponse* response = request->beginResponse(200);
|
||||||
|
addCORSHeaders(response);
|
||||||
|
request->send(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CORS request, no options => let the request pass and add CORS headers after
|
||||||
|
next();
|
||||||
|
AsyncWebServerResponse* response = request->getResponse();
|
||||||
|
if (response) {
|
||||||
|
addCORSHeaders(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// NO Origin header => no CORS handling
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RateLimitMiddleware::isRequestAllowed(uint32_t& retryAfterSeconds) {
|
||||||
|
uint32_t now = millis();
|
||||||
|
|
||||||
|
while (!_requestTimes.empty() && _requestTimes.front() <= now - _windowSizeMillis)
|
||||||
|
_requestTimes.pop_front();
|
||||||
|
|
||||||
|
_requestTimes.push_back(now);
|
||||||
|
|
||||||
|
if (_requestTimes.size() > _maxRequests) {
|
||||||
|
_requestTimes.pop_front();
|
||||||
|
retryAfterSeconds = (_windowSizeMillis - (now - _requestTimes.front())) / 1000 + 1;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
retryAfterSeconds = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RateLimitMiddleware::run(AsyncWebServerRequest* request, ArMiddlewareNext next) {
|
||||||
|
uint32_t retryAfterSeconds;
|
||||||
|
if (isRequestAllowed(retryAfterSeconds)) {
|
||||||
|
next();
|
||||||
|
} else {
|
||||||
|
AsyncWebServerResponse* response = request->beginResponse(429);
|
||||||
|
response->addHeader(F("Retry-After"), retryAfterSeconds);
|
||||||
|
request->send(response);
|
||||||
|
}
|
||||||
|
}
|
@ -1,202 +0,0 @@
|
|||||||
/*
|
|
||||||
Asynchronous WebServer library for Espressif MCUs
|
|
||||||
|
|
||||||
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
|
|
||||||
This file is part of the esp8266 core for Arduino environment.
|
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
|
||||||
modify it under the terms of the GNU Lesser General Public
|
|
||||||
License as published by the Free Software Foundation; either
|
|
||||||
version 2.1 of the License, or (at your option) any later version.
|
|
||||||
|
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
Lesser General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU Lesser General Public
|
|
||||||
License along with this library; if not, write to the Free Software
|
|
||||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
*/
|
|
||||||
#ifndef STRINGARRAY_H_
|
|
||||||
#define STRINGARRAY_H_
|
|
||||||
|
|
||||||
#include "stddef.h"
|
|
||||||
#include "WString.h"
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
class LinkedListNode {
|
|
||||||
T _value;
|
|
||||||
public:
|
|
||||||
LinkedListNode<T>* next;
|
|
||||||
LinkedListNode(const T val): _value(val), next(nullptr) {}
|
|
||||||
~LinkedListNode(){}
|
|
||||||
const T& value() const { return _value; };
|
|
||||||
T& value(){ return _value; }
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename T, template<typename> class Item = LinkedListNode>
|
|
||||||
class LinkedList {
|
|
||||||
public:
|
|
||||||
typedef Item<T> ItemType;
|
|
||||||
typedef std::function<void(const T&)> OnRemove;
|
|
||||||
typedef std::function<bool(const T&)> Predicate;
|
|
||||||
private:
|
|
||||||
ItemType* _root;
|
|
||||||
OnRemove _onRemove;
|
|
||||||
|
|
||||||
class Iterator {
|
|
||||||
ItemType* _node;
|
|
||||||
ItemType* _nextNode = nullptr;
|
|
||||||
public:
|
|
||||||
Iterator(ItemType* current = nullptr) : _node(current) {
|
|
||||||
if (_node != nullptr) {
|
|
||||||
_nextNode = current->next;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Iterator(const Iterator& i) : _node(i._node) {}
|
|
||||||
Iterator& operator ++() {
|
|
||||||
_node = _nextNode;
|
|
||||||
_nextNode = _node != nullptr ? _node->next : nullptr;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
class StringArray : public LinkedList<String> {
|
|
||||||
public:
|
|
||||||
|
|
||||||
StringArray() : LinkedList(nullptr) {}
|
|
||||||
|
|
||||||
bool containsIgnoreCase(const String& str){
|
|
||||||
for (const auto& s : *this) {
|
|
||||||
if (str.equalsIgnoreCase(s)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#endif /* STRINGARRAY_H_ */
|
|
@ -20,216 +20,218 @@
|
|||||||
*/
|
*/
|
||||||
#include "WebAuthentication.h"
|
#include "WebAuthentication.h"
|
||||||
#include <libb64/cencode.h>
|
#include <libb64/cencode.h>
|
||||||
#ifdef ESP32
|
#if defined(ESP32) || defined(TARGET_RP2040)
|
||||||
#include "mbedtls/md5.h"
|
#include <MD5Builder.h>
|
||||||
#else
|
#else
|
||||||
#include "md5.h"
|
#include "md5.h"
|
||||||
#endif
|
#endif
|
||||||
|
#include "literals.h"
|
||||||
|
|
||||||
|
using namespace asyncsrv;
|
||||||
|
|
||||||
// Basic Auth hash = base64("username:password")
|
// Basic Auth hash = base64("username:password")
|
||||||
|
|
||||||
bool checkBasicAuthentication(const char * hash, const char * username, const char * password){
|
bool checkBasicAuthentication(const char* hash, const char* username, const char* password) {
|
||||||
if(username == NULL || password == NULL || hash == NULL)
|
if (username == NULL || password == NULL || hash == NULL)
|
||||||
return false;
|
return false;
|
||||||
|
return generateBasicHash(username, password).equalsIgnoreCase(hash);
|
||||||
|
}
|
||||||
|
|
||||||
size_t toencodeLen = strlen(username)+strlen(password)+1;
|
String generateBasicHash(const char* username, const char* password) {
|
||||||
size_t encodedLen = base64_encode_expected_len(toencodeLen);
|
if (username == NULL || password == NULL)
|
||||||
if(strlen(hash) != encodedLen)
|
return emptyString;
|
||||||
return false;
|
|
||||||
|
|
||||||
char *toencode = new char[toencodeLen+1];
|
size_t toencodeLen = strlen(username) + strlen(password) + 1;
|
||||||
if(toencode == NULL){
|
|
||||||
return false;
|
char* toencode = new char[toencodeLen + 1];
|
||||||
|
if (toencode == NULL) {
|
||||||
|
return emptyString;
|
||||||
}
|
}
|
||||||
char *encoded = new char[base64_encode_expected_len(toencodeLen)+1];
|
char* encoded = new char[base64_encode_expected_len(toencodeLen) + 1];
|
||||||
if(encoded == NULL){
|
if (encoded == NULL) {
|
||||||
delete[] toencode;
|
delete[] toencode;
|
||||||
return false;
|
return emptyString;
|
||||||
}
|
}
|
||||||
sprintf(toencode, "%s:%s", username, password);
|
sprintf_P(toencode, PSTR("%s:%s"), username, password);
|
||||||
if(base64_encode_chars(toencode, toencodeLen, encoded) > 0 && memcmp(hash, encoded, encodedLen) == 0){
|
if (base64_encode_chars(toencode, toencodeLen, encoded) > 0) {
|
||||||
|
String res = String(encoded);
|
||||||
delete[] toencode;
|
delete[] toencode;
|
||||||
delete[] encoded;
|
delete[] encoded;
|
||||||
return true;
|
return res;
|
||||||
}
|
}
|
||||||
delete[] toencode;
|
delete[] toencode;
|
||||||
delete[] encoded;
|
delete[] encoded;
|
||||||
return false;
|
return emptyString;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool getMD5(uint8_t * data, uint16_t len, char * output){//33 bytes or more
|
static bool getMD5(uint8_t* data, uint16_t len, char* output) { // 33 bytes or more
|
||||||
#ifdef ESP32
|
#if defined(ESP32) || defined(TARGET_RP2040)
|
||||||
mbedtls_md5_context _ctx;
|
MD5Builder md5;
|
||||||
|
md5.begin();
|
||||||
|
md5.add(data, len);
|
||||||
|
md5.calculate();
|
||||||
|
md5.getChars(output);
|
||||||
#else
|
#else
|
||||||
md5_context_t _ctx;
|
md5_context_t _ctx;
|
||||||
#endif
|
|
||||||
uint8_t i;
|
uint8_t* _buf = (uint8_t*)malloc(16);
|
||||||
uint8_t * _buf = (uint8_t*)malloc(16);
|
if (_buf == NULL)
|
||||||
if(_buf == NULL)
|
|
||||||
return false;
|
return false;
|
||||||
memset(_buf, 0x00, 16);
|
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);
|
MD5Init(&_ctx);
|
||||||
MD5Update(&_ctx, data, len);
|
MD5Update(&_ctx, data, len);
|
||||||
MD5Final(_buf, &_ctx);
|
MD5Final(_buf, &_ctx);
|
||||||
#endif
|
|
||||||
for(i = 0; i < 16; i++) {
|
for (uint8_t i = 0; i < 16; i++) {
|
||||||
sprintf(output + (i * 2), "%02x", _buf[i]);
|
sprintf_P(output + (i * 2), PSTR("%02x"), _buf[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
free(_buf);
|
free(_buf);
|
||||||
|
#endif
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static String genRandomMD5(){
|
String genRandomMD5() {
|
||||||
#ifdef ESP8266
|
#ifdef ESP8266
|
||||||
uint32_t r = RANDOM_REG32;
|
uint32_t r = RANDOM_REG32;
|
||||||
#else
|
#else
|
||||||
uint32_t r = rand();
|
uint32_t r = rand();
|
||||||
#endif
|
#endif
|
||||||
char * out = (char*)malloc(33);
|
char* out = (char*)malloc(33);
|
||||||
if(out == NULL || !getMD5((uint8_t*)(&r), 4, out))
|
if (out == NULL || !getMD5((uint8_t*)(&r), 4, out))
|
||||||
return "";
|
return emptyString;
|
||||||
String res = String(out);
|
String res = String(out);
|
||||||
free(out);
|
free(out);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
static String stringMD5(const String& in){
|
static String stringMD5(const String& in) {
|
||||||
char * out = (char*)malloc(33);
|
char* out = (char*)malloc(33);
|
||||||
if(out == NULL || !getMD5((uint8_t*)(in.c_str()), in.length(), out))
|
if (out == NULL || !getMD5((uint8_t*)(in.c_str()), in.length(), out))
|
||||||
return "";
|
return emptyString;
|
||||||
String res = String(out);
|
String res = String(out);
|
||||||
free(out);
|
free(out);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
String generateDigestHash(const char * username, const char * password, const char * realm){
|
String generateDigestHash(const char* username, const char* password, const char* realm) {
|
||||||
if(username == NULL || password == NULL || realm == NULL){
|
if (username == NULL || password == NULL || realm == NULL) {
|
||||||
return "";
|
return emptyString;
|
||||||
}
|
}
|
||||||
char * out = (char*)malloc(33);
|
char* out = (char*)malloc(33);
|
||||||
String res = String(username);
|
|
||||||
res.concat(":");
|
String in;
|
||||||
res.concat(realm);
|
in.reserve(strlen(username) + strlen(realm) + strlen(password) + 2);
|
||||||
res.concat(":");
|
in.concat(username);
|
||||||
String in = res;
|
in.concat(':');
|
||||||
|
in.concat(realm);
|
||||||
|
in.concat(':');
|
||||||
in.concat(password);
|
in.concat(password);
|
||||||
if(out == NULL || !getMD5((uint8_t*)(in.c_str()), in.length(), out))
|
|
||||||
return "";
|
if (out == NULL || !getMD5((uint8_t*)(in.c_str()), in.length(), out))
|
||||||
res.concat(out);
|
return emptyString;
|
||||||
|
|
||||||
|
in = String(out);
|
||||||
free(out);
|
free(out);
|
||||||
return res;
|
return in;
|
||||||
}
|
}
|
||||||
|
|
||||||
String requestDigestAuthentication(const char * realm){
|
#ifndef ESP8266
|
||||||
String header = "realm=\"";
|
bool checkDigestAuthentication(const char* header, const char* method, const char* username, const char* password, const char* realm, bool passwordIsHash, const char* nonce, const char* opaque, const char* uri)
|
||||||
if(realm == NULL)
|
#else
|
||||||
header.concat("asyncesp");
|
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)
|
||||||
else
|
#endif
|
||||||
header.concat(realm);
|
{
|
||||||
header.concat( "\", qop=\"auth\", nonce=\"");
|
if (username == NULL || password == NULL || header == NULL || method == NULL) {
|
||||||
header.concat(genRandomMD5());
|
// os_printf("AUTH FAIL: missing requred fields\n");
|
||||||
header.concat("\", opaque=\"");
|
|
||||||
header.concat(genRandomMD5());
|
|
||||||
header.concat("\"");
|
|
||||||
return header;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool checkDigestAuthentication(const char * header, const char * method, const char * username, const char * password, const char * realm, bool passwordIsHash, const char * nonce, const char * opaque, const char * uri){
|
|
||||||
if(username == NULL || password == NULL || header == NULL || method == NULL){
|
|
||||||
//os_printf("AUTH FAIL: missing requred fields\n");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
String myHeader = String(header);
|
String myHeader(header);
|
||||||
int nextBreak = myHeader.indexOf(",");
|
int nextBreak = myHeader.indexOf(',');
|
||||||
if(nextBreak < 0){
|
if (nextBreak < 0) {
|
||||||
//os_printf("AUTH FAIL: no variables\n");
|
// os_printf("AUTH FAIL: no variables\n");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
String myUsername = String();
|
String myUsername;
|
||||||
String myRealm = String();
|
String myRealm;
|
||||||
String myNonce = String();
|
String myNonce;
|
||||||
String myUri = String();
|
String myUri;
|
||||||
String myResponse = String();
|
String myResponse;
|
||||||
String myQop = String();
|
String myQop;
|
||||||
String myNc = String();
|
String myNc;
|
||||||
String myCnonce = String();
|
String myCnonce;
|
||||||
|
|
||||||
myHeader += ", ";
|
myHeader += (char)0x2c; // ','
|
||||||
|
myHeader += (char)0x20; // ' '
|
||||||
do {
|
do {
|
||||||
String avLine = myHeader.substring(0, nextBreak);
|
String avLine(myHeader.substring(0, nextBreak));
|
||||||
avLine.trim();
|
avLine.trim();
|
||||||
myHeader = myHeader.substring(nextBreak+1);
|
myHeader = myHeader.substring(nextBreak + 1);
|
||||||
nextBreak = myHeader.indexOf(",");
|
nextBreak = myHeader.indexOf(',');
|
||||||
|
|
||||||
int eqSign = avLine.indexOf("=");
|
int eqSign = avLine.indexOf('=');
|
||||||
if(eqSign < 0){
|
if (eqSign < 0) {
|
||||||
//os_printf("AUTH FAIL: no = sign\n");
|
// os_printf("AUTH FAIL: no = sign\n");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
String varName = avLine.substring(0, eqSign);
|
String varName(avLine.substring(0, eqSign));
|
||||||
avLine = avLine.substring(eqSign + 1);
|
avLine = avLine.substring(eqSign + 1);
|
||||||
if(avLine.startsWith("\"")){
|
if (avLine.startsWith(String('"'))) {
|
||||||
avLine = avLine.substring(1, avLine.length() - 1);
|
avLine = avLine.substring(1, avLine.length() - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(varName.equals("username")){
|
if (varName.equals(T_username)) {
|
||||||
if(!avLine.equals(username)){
|
if (!avLine.equals(username)) {
|
||||||
//os_printf("AUTH FAIL: username\n");
|
// os_printf("AUTH FAIL: username\n");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
myUsername = avLine;
|
myUsername = avLine;
|
||||||
} else if(varName.equals("realm")){
|
} else if (varName.equals(T_realm)) {
|
||||||
if(realm != NULL && !avLine.equals(realm)){
|
if (realm != NULL && !avLine.equals(realm)) {
|
||||||
//os_printf("AUTH FAIL: realm\n");
|
// os_printf("AUTH FAIL: realm\n");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
myRealm = avLine;
|
myRealm = avLine;
|
||||||
} else if(varName.equals("nonce")){
|
} else if (varName.equals(T_nonce)) {
|
||||||
if(nonce != NULL && !avLine.equals(nonce)){
|
if (nonce != NULL && !avLine.equals(nonce)) {
|
||||||
//os_printf("AUTH FAIL: nonce\n");
|
// os_printf("AUTH FAIL: nonce\n");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
myNonce = avLine;
|
myNonce = avLine;
|
||||||
} else if(varName.equals("opaque")){
|
} else if (varName.equals(T_opaque)) {
|
||||||
if(opaque != NULL && !avLine.equals(opaque)){
|
if (opaque != NULL && !avLine.equals(opaque)) {
|
||||||
//os_printf("AUTH FAIL: opaque\n");
|
// os_printf("AUTH FAIL: opaque\n");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if(varName.equals("uri")){
|
} else if (varName.equals(T_uri)) {
|
||||||
if(uri != NULL && !avLine.equals(uri)){
|
if (uri != NULL && !avLine.equals(uri)) {
|
||||||
//os_printf("AUTH FAIL: uri\n");
|
// os_printf("AUTH FAIL: uri\n");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
myUri = avLine;
|
myUri = avLine;
|
||||||
} else if(varName.equals("response")){
|
} else if (varName.equals(T_response)) {
|
||||||
myResponse = avLine;
|
myResponse = avLine;
|
||||||
} else if(varName.equals("qop")){
|
} else if (varName.equals(T_qop)) {
|
||||||
myQop = avLine;
|
myQop = avLine;
|
||||||
} else if(varName.equals("nc")){
|
} else if (varName.equals(T_nc)) {
|
||||||
myNc = avLine;
|
myNc = avLine;
|
||||||
} else if(varName.equals("cnonce")){
|
} else if (varName.equals(T_cnonce)) {
|
||||||
myCnonce = avLine;
|
myCnonce = avLine;
|
||||||
}
|
}
|
||||||
} while(nextBreak > 0);
|
} while (nextBreak > 0);
|
||||||
|
|
||||||
String ha1 = (passwordIsHash) ? String(password) : stringMD5(myUsername + ":" + myRealm + ":" + String(password));
|
String ha1 = passwordIsHash ? password : stringMD5(myUsername + ':' + myRealm + ':' + password).c_str();
|
||||||
String ha2 = String(method) + ":" + myUri;
|
String ha2 = stringMD5(String(method) + ':' + myUri);
|
||||||
String response = ha1 + ":" + myNonce + ":" + myNc + ":" + myCnonce + ":" + myQop + ":" + stringMD5(ha2);
|
String response = ha1 + ':' + myNonce + ':' + myNc + ':' + myCnonce + ':' + myQop + ':' + ha2;
|
||||||
|
|
||||||
if(myResponse.equals(stringMD5(response))){
|
if (myResponse.equals(stringMD5(response))) {
|
||||||
//os_printf("AUTH SUCCESS\n");
|
// os_printf("AUTH SUCCESS\n");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
//os_printf("AUTH FAIL: password\n");
|
// os_printf("AUTH FAIL: password\n");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -24,11 +24,19 @@
|
|||||||
|
|
||||||
#include "Arduino.h"
|
#include "Arduino.h"
|
||||||
|
|
||||||
bool checkBasicAuthentication(const char * header, const char * username, const char * password);
|
bool checkBasicAuthentication(const char* header, const char* username, const char* password);
|
||||||
String requestDigestAuthentication(const char * realm);
|
|
||||||
bool checkDigestAuthentication(const char * header, const char * method, const char * username, const char * password, const char * realm, bool passwordIsHash, const char * nonce, const char * opaque, const char * uri);
|
|
||||||
|
|
||||||
//for storing hashed versions on the device that can be authenticated against
|
bool checkDigestAuthentication(const char* header, const char* method, const char* username, const char* password, const char* realm, bool passwordIsHash, const char* nonce, const char* opaque, const char* uri);
|
||||||
String generateDigestHash(const char * username, const char * password, const char * realm);
|
|
||||||
|
#ifdef ESP8266
|
||||||
|
bool checkDigestAuthentication(const char* header, const __FlashStringHelper* method, const char* username, const char* password, const char* realm, bool passwordIsHash, const char* nonce, const char* opaque, const char* uri);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// for storing hashed versions on the device that can be authenticated against
|
||||||
|
String generateDigestHash(const char* username, const char* password, const char* realm);
|
||||||
|
|
||||||
|
String generateBasicHash(const char* username, const char* password);
|
||||||
|
|
||||||
|
String genRandomMD5();
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -23,19 +23,21 @@
|
|||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#ifdef ASYNCWEBSERVER_REGEX
|
#ifdef ASYNCWEBSERVER_REGEX
|
||||||
#include <regex>
|
#include <regex>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "stddef.h"
|
#include "stddef.h"
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
|
||||||
class AsyncStaticWebHandler: public AsyncWebHandler {
|
class AsyncStaticWebHandler : public AsyncWebHandler {
|
||||||
using File = fs::File;
|
using File = fs::File;
|
||||||
using FS = fs::FS;
|
using FS = fs::FS;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool _getFile(AsyncWebServerRequest *request);
|
bool _getFile(AsyncWebServerRequest* request);
|
||||||
bool _fileExists(AsyncWebServerRequest *request, const String& path);
|
bool _fileExists(AsyncWebServerRequest* request, const String& path);
|
||||||
uint8_t _countBits(const uint8_t value) const;
|
uint8_t _countBits(const uint8_t value) const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
FS _fs;
|
FS _fs;
|
||||||
String _uri;
|
String _uri;
|
||||||
@ -47,23 +49,24 @@ class AsyncStaticWebHandler: public AsyncWebHandler {
|
|||||||
bool _isDir;
|
bool _isDir;
|
||||||
bool _gzipFirst;
|
bool _gzipFirst;
|
||||||
uint8_t _gzipStats;
|
uint8_t _gzipStats;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AsyncStaticWebHandler(const char* uri, FS& fs, const char* path, const char* cache_control);
|
AsyncStaticWebHandler(const char* uri, FS& fs, const char* path, const char* cache_control);
|
||||||
virtual bool canHandle(AsyncWebServerRequest *request) override final;
|
virtual bool canHandle(AsyncWebServerRequest* request) override final;
|
||||||
virtual void handleRequest(AsyncWebServerRequest *request) override final;
|
virtual void handleRequest(AsyncWebServerRequest* request) override final;
|
||||||
AsyncStaticWebHandler& setIsDir(bool isDir);
|
AsyncStaticWebHandler& setIsDir(bool isDir);
|
||||||
AsyncStaticWebHandler& setDefaultFile(const char* filename);
|
AsyncStaticWebHandler& setDefaultFile(const char* filename);
|
||||||
AsyncStaticWebHandler& setCacheControl(const char* cache_control);
|
AsyncStaticWebHandler& setCacheControl(const char* cache_control);
|
||||||
AsyncStaticWebHandler& setLastModified(const char* last_modified);
|
AsyncStaticWebHandler& setLastModified(const char* last_modified);
|
||||||
AsyncStaticWebHandler& setLastModified(struct tm* last_modified);
|
AsyncStaticWebHandler& setLastModified(struct tm* last_modified);
|
||||||
#ifdef ESP8266
|
#ifdef ESP8266
|
||||||
AsyncStaticWebHandler& setLastModified(time_t last_modified);
|
AsyncStaticWebHandler& setLastModified(time_t last_modified);
|
||||||
AsyncStaticWebHandler& setLastModified(); //sets to current time. Make sure sntp is runing and time is updated
|
AsyncStaticWebHandler& setLastModified(); // sets to current time. Make sure sntp is runing and time is updated
|
||||||
#endif
|
#endif
|
||||||
AsyncStaticWebHandler& setTemplateProcessor(AwsTemplateProcessor newCallback) {_callback = newCallback; return *this;}
|
AsyncStaticWebHandler& setTemplateProcessor(AwsTemplateProcessor newCallback);
|
||||||
};
|
};
|
||||||
|
|
||||||
class AsyncCallbackWebHandler: public AsyncWebHandler {
|
class AsyncCallbackWebHandler : public AsyncWebHandler {
|
||||||
private:
|
private:
|
||||||
protected:
|
protected:
|
||||||
String _uri;
|
String _uri;
|
||||||
@ -72,80 +75,20 @@ class AsyncCallbackWebHandler: public AsyncWebHandler {
|
|||||||
ArUploadHandlerFunction _onUpload;
|
ArUploadHandlerFunction _onUpload;
|
||||||
ArBodyHandlerFunction _onBody;
|
ArBodyHandlerFunction _onBody;
|
||||||
bool _isRegex;
|
bool _isRegex;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AsyncCallbackWebHandler() : _uri(), _method(HTTP_ANY), _onRequest(NULL), _onUpload(NULL), _onBody(NULL), _isRegex(false) {}
|
AsyncCallbackWebHandler() : _uri(), _method(HTTP_ANY), _onRequest(NULL), _onUpload(NULL), _onBody(NULL), _isRegex(false) {}
|
||||||
void setUri(const String& uri){
|
void setUri(const String& uri);
|
||||||
_uri = uri;
|
void setMethod(WebRequestMethodComposite method) { _method = method; }
|
||||||
_isRegex = uri.startsWith("^") && uri.endsWith("$");
|
void onRequest(ArRequestHandlerFunction fn) { _onRequest = fn; }
|
||||||
}
|
void onUpload(ArUploadHandlerFunction fn) { _onUpload = fn; }
|
||||||
void setMethod(WebRequestMethodComposite method){ _method = method; }
|
void onBody(ArBodyHandlerFunction fn) { _onBody = fn; }
|
||||||
void onRequest(ArRequestHandlerFunction fn){ _onRequest = fn; }
|
|
||||||
void onUpload(ArUploadHandlerFunction fn){ _onUpload = fn; }
|
|
||||||
void onBody(ArBodyHandlerFunction fn){ _onBody = fn; }
|
|
||||||
|
|
||||||
virtual bool canHandle(AsyncWebServerRequest *request) override final{
|
virtual bool canHandle(AsyncWebServerRequest* request) override final;
|
||||||
|
virtual void handleRequest(AsyncWebServerRequest* request) override final;
|
||||||
if(!_onRequest)
|
virtual void handleUpload(AsyncWebServerRequest* request, const String& filename, size_t index, uint8_t* data, size_t len, bool final) override final;
|
||||||
return false;
|
virtual void handleBody(AsyncWebServerRequest* request, uint8_t* data, size_t len, size_t index, size_t total) override final;
|
||||||
|
virtual bool isRequestHandlerTrivial() override final { return !_onRequest; }
|
||||||
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_ */
|
#endif /* ASYNCWEBSERVERHANDLERIMPL_H_ */
|
||||||
|
@ -21,94 +21,102 @@
|
|||||||
#include "ESPAsyncWebServer.h"
|
#include "ESPAsyncWebServer.h"
|
||||||
#include "WebHandlerImpl.h"
|
#include "WebHandlerImpl.h"
|
||||||
|
|
||||||
|
using namespace asyncsrv;
|
||||||
|
|
||||||
|
AsyncWebHandler& AsyncWebHandler::setFilter(ArRequestFilterFunction fn) {
|
||||||
|
_filter = fn;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
AsyncWebHandler& AsyncWebHandler::setAuthentication(const char* username, const char* password) {
|
||||||
|
if (!_authMiddleware) {
|
||||||
|
_authMiddleware = new AuthenticationMiddleware();
|
||||||
|
_authMiddleware->_freeOnRemoval = true;
|
||||||
|
addMiddleware(_authMiddleware);
|
||||||
|
}
|
||||||
|
_authMiddleware->setUsername(username);
|
||||||
|
_authMiddleware->setPassword(password);
|
||||||
|
return *this;
|
||||||
|
};
|
||||||
|
|
||||||
AsyncStaticWebHandler::AsyncStaticWebHandler(const char* uri, FS& fs, const char* path, const char* cache_control)
|
AsyncStaticWebHandler::AsyncStaticWebHandler(const char* uri, FS& fs, const char* path, const char* cache_control)
|
||||||
: _fs(fs), _uri(uri), _path(path), _default_file("index.htm"), _cache_control(cache_control), _last_modified(""), _callback(nullptr)
|
: _fs(fs), _uri(uri), _path(path), _default_file(F("index.htm")), _cache_control(cache_control), _last_modified(), _callback(nullptr) {
|
||||||
{
|
|
||||||
// Ensure leading '/'
|
// Ensure leading '/'
|
||||||
if (_uri.length() == 0 || _uri[0] != '/') _uri = "/" + _uri;
|
if (_uri.length() == 0 || _uri[0] != '/')
|
||||||
if (_path.length() == 0 || _path[0] != '/') _path = "/" + _path;
|
_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.
|
// 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.
|
// However - if it does not end with '/' we, can't assume a file, path can still be a directory.
|
||||||
_isDir = _path[_path.length()-1] == '/';
|
_isDir = _path[_path.length() - 1] == '/';
|
||||||
|
|
||||||
// Remove the trailing '/' so we can handle default file
|
// Remove the trailing '/' so we can handle default file
|
||||||
// Notice that root will be "" not "/"
|
// Notice that root will be "" not "/"
|
||||||
if (_uri[_uri.length()-1] == '/') _uri = _uri.substring(0, _uri.length()-1);
|
if (_uri[_uri.length() - 1] == '/')
|
||||||
if (_path[_path.length()-1] == '/') _path = _path.substring(0, _path.length()-1);
|
_uri = _uri.substring(0, _uri.length() - 1);
|
||||||
|
if (_path[_path.length() - 1] == '/')
|
||||||
|
_path = _path.substring(0, _path.length() - 1);
|
||||||
|
|
||||||
// Reset stats
|
// Reset stats
|
||||||
_gzipFirst = false;
|
_gzipFirst = false;
|
||||||
_gzipStats = 0xF8;
|
_gzipStats = 0xF8;
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncStaticWebHandler& AsyncStaticWebHandler::setIsDir(bool isDir){
|
AsyncStaticWebHandler& AsyncStaticWebHandler::setIsDir(bool isDir) {
|
||||||
_isDir = isDir;
|
_isDir = isDir;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncStaticWebHandler& AsyncStaticWebHandler::setDefaultFile(const char* filename){
|
AsyncStaticWebHandler& AsyncStaticWebHandler::setDefaultFile(const char* filename) {
|
||||||
_default_file = String(filename);
|
_default_file = String(filename);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncStaticWebHandler& AsyncStaticWebHandler::setCacheControl(const char* cache_control){
|
AsyncStaticWebHandler& AsyncStaticWebHandler::setCacheControl(const char* cache_control) {
|
||||||
_cache_control = String(cache_control);
|
_cache_control = String(cache_control);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(const char* last_modified){
|
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(const char* last_modified) {
|
||||||
_last_modified = String(last_modified);
|
_last_modified = last_modified;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(struct tm* last_modified){
|
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(struct tm* last_modified) {
|
||||||
|
auto formatP = PSTR("%a, %d %b %Y %H:%M:%S %Z");
|
||||||
|
char format[strlen_P(formatP) + 1];
|
||||||
|
strcpy_P(format, formatP);
|
||||||
|
|
||||||
char result[30];
|
char result[30];
|
||||||
strftime (result,30,"%a, %d %b %Y %H:%M:%S %Z", last_modified);
|
strftime(result, sizeof(result), format, last_modified);
|
||||||
return setLastModified((const char *)result);
|
return setLastModified((const char*)result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef ESP8266
|
#ifdef ESP8266
|
||||||
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(time_t last_modified){
|
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(time_t last_modified) {
|
||||||
return setLastModified((struct tm *)gmtime(&last_modified));
|
return setLastModified((struct tm*)gmtime(&last_modified));
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(){
|
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified() {
|
||||||
time_t last_modified;
|
time_t last_modified;
|
||||||
if(time(&last_modified) == 0) //time is not yet set
|
if (time(&last_modified) == 0) // time is not yet set
|
||||||
return *this;
|
return *this;
|
||||||
return setLastModified(last_modified);
|
return setLastModified(last_modified);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
bool AsyncStaticWebHandler::canHandle(AsyncWebServerRequest *request){
|
bool AsyncStaticWebHandler::canHandle(AsyncWebServerRequest* request) {
|
||||||
if(request->method() != HTTP_GET
|
if (request->method() != HTTP_GET || !request->url().startsWith(_uri) || !request->isExpectedRequestedConnType(RCT_DEFAULT, RCT_HTTP)) {
|
||||||
|| !request->url().startsWith(_uri)
|
|
||||||
|| !request->isExpectedRequestedConnType(RCT_DEFAULT, RCT_HTTP)
|
|
||||||
){
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (_getFile(request)) {
|
return _getFile(request);
|
||||||
// We interested in "If-Modified-Since" header to check if file was modified
|
|
||||||
if (_last_modified.length())
|
|
||||||
request->addInterestingHeader("If-Modified-Since");
|
|
||||||
|
|
||||||
if(_cache_control.length())
|
|
||||||
request->addInterestingHeader("If-None-Match");
|
|
||||||
|
|
||||||
DEBUGF("[AsyncStaticWebHandler::canHandle] TRUE\n");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AsyncStaticWebHandler::_getFile(AsyncWebServerRequest *request)
|
bool AsyncStaticWebHandler::_getFile(AsyncWebServerRequest* request) {
|
||||||
{
|
|
||||||
// Remove the found uri
|
// Remove the found uri
|
||||||
String path = request->url().substring(_uri.length());
|
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 '/'
|
// We can skip the file check and look for default if request is to the root of a directory or that request path ends with '/'
|
||||||
bool canSkipFileCheck = (_isDir && path.length() == 0) || (path.length() && path[path.length()-1] == '/');
|
bool canSkipFileCheck = (_isDir && path.length() == 0) || (path.length() && path[path.length() - 1] == '/');
|
||||||
|
|
||||||
path = _path + path;
|
path = _path + path;
|
||||||
|
|
||||||
@ -121,39 +129,46 @@ bool AsyncStaticWebHandler::_getFile(AsyncWebServerRequest *request)
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Try to add default file, ensure there is a trailing '/' ot the path.
|
// Try to add default file, ensure there is a trailing '/' ot the path.
|
||||||
if (path.length() == 0 || path[path.length()-1] != '/')
|
if (path.length() == 0 || path[path.length() - 1] != '/')
|
||||||
path += "/";
|
path += String('/');
|
||||||
path += _default_file;
|
path += _default_file;
|
||||||
|
|
||||||
return _fileExists(request, path);
|
return _fileExists(request, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef ESP32
|
#ifdef ESP32
|
||||||
#define FILE_IS_REAL(f) (f == true && !f.isDirectory())
|
#define FILE_IS_REAL(f) (f == true && !f.isDirectory())
|
||||||
#else
|
#else
|
||||||
#define FILE_IS_REAL(f) (f == true)
|
#define FILE_IS_REAL(f) (f == true)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
bool AsyncStaticWebHandler::_fileExists(AsyncWebServerRequest *request, const String& path)
|
bool AsyncStaticWebHandler::_fileExists(AsyncWebServerRequest* request, const String& path) {
|
||||||
{
|
|
||||||
bool fileFound = false;
|
bool fileFound = false;
|
||||||
bool gzipFound = false;
|
bool gzipFound = false;
|
||||||
|
|
||||||
String gzip = path + ".gz";
|
String gzip = path + F(".gz");
|
||||||
|
|
||||||
if (_gzipFirst) {
|
if (_gzipFirst) {
|
||||||
request->_tempFile = _fs.open(gzip, "r");
|
if (_fs.exists(gzip)) {
|
||||||
gzipFound = FILE_IS_REAL(request->_tempFile);
|
request->_tempFile = _fs.open(gzip, fs::FileOpenMode::read);
|
||||||
if (!gzipFound){
|
gzipFound = FILE_IS_REAL(request->_tempFile);
|
||||||
request->_tempFile = _fs.open(path, "r");
|
}
|
||||||
fileFound = 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 {
|
} else {
|
||||||
request->_tempFile = _fs.open(path, "r");
|
if (_fs.exists(path)) {
|
||||||
fileFound = FILE_IS_REAL(request->_tempFile);
|
request->_tempFile = _fs.open(path, fs::FileOpenMode::read);
|
||||||
if (!fileFound){
|
fileFound = FILE_IS_REAL(request->_tempFile);
|
||||||
request->_tempFile = _fs.open(gzip, "r");
|
}
|
||||||
gzipFound = 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,55 +177,71 @@ bool AsyncStaticWebHandler::_fileExists(AsyncWebServerRequest *request, const St
|
|||||||
if (found) {
|
if (found) {
|
||||||
// Extract the file name from the path and keep it in _tempObject
|
// Extract the file name from the path and keep it in _tempObject
|
||||||
size_t pathLen = path.length();
|
size_t pathLen = path.length();
|
||||||
char * _tempPath = (char*)malloc(pathLen+1);
|
char* _tempPath = (char*)malloc(pathLen + 1);
|
||||||
snprintf(_tempPath, pathLen+1, "%s", path.c_str());
|
snprintf_P(_tempPath, pathLen + 1, PSTR("%s"), path.c_str());
|
||||||
request->_tempObject = (void*)_tempPath;
|
request->_tempObject = (void*)_tempPath;
|
||||||
|
|
||||||
// Calculate gzip statistic
|
// Calculate gzip statistic
|
||||||
_gzipStats = (_gzipStats << 1) + (gzipFound ? 1 : 0);
|
_gzipStats = (_gzipStats << 1) + (gzipFound ? 1 : 0);
|
||||||
if (_gzipStats == 0x00) _gzipFirst = false; // All files are not gzip
|
if (_gzipStats == 0x00)
|
||||||
else if (_gzipStats == 0xFF) _gzipFirst = true; // All files are gzip
|
_gzipFirst = false; // All files are not gzip
|
||||||
else _gzipFirst = _countBits(_gzipStats) > 4; // IF we have more gzip files - try gzip first
|
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;
|
return found;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t AsyncStaticWebHandler::_countBits(const uint8_t value) const
|
uint8_t AsyncStaticWebHandler::_countBits(const uint8_t value) const {
|
||||||
{
|
|
||||||
uint8_t w = value;
|
uint8_t w = value;
|
||||||
uint8_t n;
|
uint8_t n;
|
||||||
for (n=0; w!=0; n++) w&=w-1;
|
for (n = 0; w != 0; n++)
|
||||||
|
w &= w - 1;
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest *request)
|
void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest* request) {
|
||||||
{
|
|
||||||
// Get the filename from request->_tempObject and free it
|
// Get the filename from request->_tempObject and free it
|
||||||
String filename = String((char*)request->_tempObject);
|
String filename = String((char*)request->_tempObject);
|
||||||
free(request->_tempObject);
|
free(request->_tempObject);
|
||||||
request->_tempObject = NULL;
|
request->_tempObject = NULL;
|
||||||
if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str()))
|
|
||||||
return request->requestAuthentication();
|
|
||||||
|
|
||||||
if (request->_tempFile == true) {
|
if (request->_tempFile == true) {
|
||||||
String etag = String(request->_tempFile.size());
|
time_t lw = request->_tempFile.getLastWrite(); // get last file mod time (if supported by FS)
|
||||||
if (_last_modified.length() && _last_modified == request->header("If-Modified-Since")) {
|
// set etag to lastmod timestamp if available, otherwise to size
|
||||||
|
String etag;
|
||||||
|
if (lw) {
|
||||||
|
setLastModified(gmtime(&lw));
|
||||||
|
#if defined(TARGET_RP2040)
|
||||||
|
// time_t == long long int
|
||||||
|
const size_t len = 1 + 8 * sizeof(time_t);
|
||||||
|
char buf[len];
|
||||||
|
char* ret = lltoa(lw, buf, len, 10);
|
||||||
|
etag = ret ? String(ret) : String(request->_tempFile.size());
|
||||||
|
#else
|
||||||
|
etag = String(lw);
|
||||||
|
#endif
|
||||||
|
} else {
|
||||||
|
etag = String(request->_tempFile.size());
|
||||||
|
}
|
||||||
|
if (_last_modified.length() && _last_modified == request->header(T_IMS)) {
|
||||||
request->_tempFile.close();
|
request->_tempFile.close();
|
||||||
request->send(304); // Not modified
|
request->send(304); // Not modified
|
||||||
} else if (_cache_control.length() && request->hasHeader("If-None-Match") && request->header("If-None-Match").equals(etag)) {
|
} else if (_cache_control.length() && request->hasHeader(T_INM) && request->header(T_INM).equals(etag)) {
|
||||||
request->_tempFile.close();
|
request->_tempFile.close();
|
||||||
AsyncWebServerResponse * response = new AsyncBasicResponse(304); // Not modified
|
AsyncWebServerResponse* response = new AsyncBasicResponse(304); // Not modified
|
||||||
response->addHeader("Cache-Control", _cache_control);
|
response->addHeader(T_Cache_Control, _cache_control.c_str());
|
||||||
response->addHeader("ETag", etag);
|
response->addHeader(T_ETag, etag.c_str());
|
||||||
request->send(response);
|
request->send(response);
|
||||||
} else {
|
} else {
|
||||||
AsyncWebServerResponse * response = new AsyncFileResponse(request->_tempFile, filename, String(), false, _callback);
|
AsyncWebServerResponse* response = new AsyncFileResponse(request->_tempFile, filename, String(), false, _callback);
|
||||||
if (_last_modified.length())
|
if (_last_modified.length())
|
||||||
response->addHeader("Last-Modified", _last_modified);
|
response->addHeader(T_Last_Modified, _last_modified.c_str());
|
||||||
if (_cache_control.length()){
|
if (_cache_control.length()) {
|
||||||
response->addHeader("Cache-Control", _cache_control);
|
response->addHeader(T_Cache_Control, _cache_control.c_str());
|
||||||
response->addHeader("ETag", etag);
|
response->addHeader(T_ETag, etag.c_str());
|
||||||
}
|
}
|
||||||
request->send(response);
|
request->send(response);
|
||||||
}
|
}
|
||||||
@ -218,3 +249,65 @@ void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest *request)
|
|||||||
request->send(404);
|
request->send(404);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AsyncStaticWebHandler& AsyncStaticWebHandler::setTemplateProcessor(AwsTemplateProcessor newCallback) {
|
||||||
|
_callback = newCallback;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncCallbackWebHandler::setUri(const String& uri) {
|
||||||
|
_uri = uri;
|
||||||
|
_isRegex = uri.startsWith("^") && uri.endsWith("$");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AsyncCallbackWebHandler::canHandle(AsyncWebServerRequest* request) {
|
||||||
|
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;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncCallbackWebHandler::handleRequest(AsyncWebServerRequest* request) {
|
||||||
|
if (_onRequest)
|
||||||
|
_onRequest(request);
|
||||||
|
else
|
||||||
|
request->send(500);
|
||||||
|
}
|
||||||
|
void AsyncCallbackWebHandler::handleUpload(AsyncWebServerRequest* request, const String& filename, size_t index, uint8_t* data, size_t len, bool final) {
|
||||||
|
if (_onUpload)
|
||||||
|
_onUpload(request, filename, index, data, len, final);
|
||||||
|
}
|
||||||
|
void AsyncCallbackWebHandler::handleBody(AsyncWebServerRequest* request, uint8_t* data, size_t len, size_t index, size_t total) {
|
||||||
|
if (_onBody)
|
||||||
|
_onBody(request, data, len, index, total);
|
||||||
|
}
|
1175
src/WebRequest.cpp
1175
src/WebRequest.cpp
File diff suppressed because it is too large
Load Diff
@ -22,24 +22,29 @@
|
|||||||
#define ASYNCWEBSERVERRESPONSEIMPL_H_
|
#define ASYNCWEBSERVERRESPONSEIMPL_H_
|
||||||
|
|
||||||
#ifdef Arduino_h
|
#ifdef Arduino_h
|
||||||
// arduino is not compatible with std::vector
|
// arduino is not compatible with std::vector
|
||||||
#undef min
|
#undef min
|
||||||
#undef max
|
#undef max
|
||||||
#endif
|
#endif
|
||||||
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include "literals.h"
|
||||||
|
|
||||||
// It is possible to restore these defines, but one can use _min and _max instead. Or std::min, std::max.
|
// It is possible to restore these defines, but one can use _min and _max instead. Or std::min, std::max.
|
||||||
|
|
||||||
class AsyncBasicResponse: public AsyncWebServerResponse {
|
class AsyncBasicResponse : public AsyncWebServerResponse {
|
||||||
private:
|
private:
|
||||||
String _content;
|
String _content;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AsyncBasicResponse(int code, const String& contentType=String(), const String& content=String());
|
explicit AsyncBasicResponse(int code, const char* contentType = asyncsrv::empty, const char* content = asyncsrv::empty);
|
||||||
void _respond(AsyncWebServerRequest *request);
|
AsyncBasicResponse(int code, const String& contentType, const String& content = emptyString) : AsyncBasicResponse(code, contentType.c_str(), content.c_str()) {}
|
||||||
size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time);
|
void _respond(AsyncWebServerRequest* request);
|
||||||
|
size_t _ack(AsyncWebServerRequest* request, size_t len, uint32_t time);
|
||||||
bool _sourceValid() const { return true; }
|
bool _sourceValid() const { return true; }
|
||||||
};
|
};
|
||||||
|
|
||||||
class AsyncAbstractResponse: public AsyncWebServerResponse {
|
class AsyncAbstractResponse : public AsyncWebServerResponse {
|
||||||
private:
|
private:
|
||||||
String _head;
|
String _head;
|
||||||
// Data is inserted into cache at begin().
|
// Data is inserted into cache at begin().
|
||||||
@ -49,86 +54,102 @@ class AsyncAbstractResponse: public AsyncWebServerResponse {
|
|||||||
std::vector<uint8_t> _cache;
|
std::vector<uint8_t> _cache;
|
||||||
size_t _readDataFromCacheOrContent(uint8_t* data, const size_t len);
|
size_t _readDataFromCacheOrContent(uint8_t* data, const size_t len);
|
||||||
size_t _fillBufferAndProcessTemplates(uint8_t* buf, size_t maxLen);
|
size_t _fillBufferAndProcessTemplates(uint8_t* buf, size_t maxLen);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
AwsTemplateProcessor _callback;
|
AwsTemplateProcessor _callback;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AsyncAbstractResponse(AwsTemplateProcessor callback=nullptr);
|
AsyncAbstractResponse(AwsTemplateProcessor callback = nullptr);
|
||||||
void _respond(AsyncWebServerRequest *request);
|
void _respond(AsyncWebServerRequest* request);
|
||||||
size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time);
|
size_t _ack(AsyncWebServerRequest* request, size_t len, uint32_t time);
|
||||||
bool _sourceValid() const { return false; }
|
bool _sourceValid() const { return false; }
|
||||||
virtual size_t _fillBuffer(uint8_t *buf __attribute__((unused)), size_t maxLen __attribute__((unused))) { return 0; }
|
virtual size_t _fillBuffer(uint8_t* buf __attribute__((unused)), size_t maxLen __attribute__((unused))) { return 0; }
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifndef TEMPLATE_PLACEHOLDER
|
#ifndef TEMPLATE_PLACEHOLDER
|
||||||
#define TEMPLATE_PLACEHOLDER '%'
|
#define TEMPLATE_PLACEHOLDER '%'
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define TEMPLATE_PARAM_NAME_LENGTH 32
|
#define TEMPLATE_PARAM_NAME_LENGTH 32
|
||||||
class AsyncFileResponse: public AsyncAbstractResponse {
|
class AsyncFileResponse : public AsyncAbstractResponse {
|
||||||
using File = fs::File;
|
using File = fs::File;
|
||||||
using FS = fs::FS;
|
using FS = fs::FS;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
File _content;
|
File _content;
|
||||||
String _path;
|
String _path;
|
||||||
void _setContentType(const String& path);
|
void _setContentTypeFromPath(const String& path);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AsyncFileResponse(FS &fs, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr);
|
AsyncFileResponse(FS& fs, const String& path, const char* contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr);
|
||||||
AsyncFileResponse(File content, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr);
|
AsyncFileResponse(FS& fs, const String& path, const String& contentType, bool download = false, AwsTemplateProcessor callback = nullptr) : AsyncFileResponse(fs, path, contentType.c_str(), download, callback) {}
|
||||||
|
AsyncFileResponse(File content, const String& path, const char* contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr);
|
||||||
|
AsyncFileResponse(File content, const String& path, const String& contentType, bool download = false, AwsTemplateProcessor callack = nullptr) : AsyncFileResponse(content, path, contentType.c_str(), download, callack) {}
|
||||||
~AsyncFileResponse();
|
~AsyncFileResponse();
|
||||||
bool _sourceValid() const { return !!(_content); }
|
bool _sourceValid() const { return !!(_content); }
|
||||||
virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override;
|
virtual size_t _fillBuffer(uint8_t* buf, size_t maxLen) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
class AsyncStreamResponse: public AsyncAbstractResponse {
|
class AsyncStreamResponse : public AsyncAbstractResponse {
|
||||||
private:
|
private:
|
||||||
Stream *_content;
|
Stream* _content;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AsyncStreamResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback=nullptr);
|
AsyncStreamResponse(Stream& stream, const char* contentType, size_t len, AwsTemplateProcessor callback = nullptr);
|
||||||
|
AsyncStreamResponse(Stream& stream, const String& contentType, size_t len, AwsTemplateProcessor callback = nullptr) : AsyncStreamResponse(stream, contentType.c_str(), len, callback) {}
|
||||||
bool _sourceValid() const { return !!(_content); }
|
bool _sourceValid() const { return !!(_content); }
|
||||||
virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override;
|
virtual size_t _fillBuffer(uint8_t* buf, size_t maxLen) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
class AsyncCallbackResponse: public AsyncAbstractResponse {
|
class AsyncCallbackResponse : public AsyncAbstractResponse {
|
||||||
private:
|
private:
|
||||||
AwsResponseFiller _content;
|
AwsResponseFiller _content;
|
||||||
size_t _filledLength;
|
size_t _filledLength;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AsyncCallbackResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr);
|
AsyncCallbackResponse(const char* contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr);
|
||||||
|
AsyncCallbackResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) : AsyncCallbackResponse(contentType.c_str(), len, callback, templateCallback) {}
|
||||||
bool _sourceValid() const { return !!(_content); }
|
bool _sourceValid() const { return !!(_content); }
|
||||||
virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override;
|
virtual size_t _fillBuffer(uint8_t* buf, size_t maxLen) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
class AsyncChunkedResponse: public AsyncAbstractResponse {
|
class AsyncChunkedResponse : public AsyncAbstractResponse {
|
||||||
private:
|
private:
|
||||||
AwsResponseFiller _content;
|
AwsResponseFiller _content;
|
||||||
size_t _filledLength;
|
size_t _filledLength;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AsyncChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr);
|
AsyncChunkedResponse(const char* contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr);
|
||||||
|
AsyncChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) : AsyncChunkedResponse(contentType.c_str(), callback, templateCallback) {}
|
||||||
bool _sourceValid() const { return !!(_content); }
|
bool _sourceValid() const { return !!(_content); }
|
||||||
virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override;
|
virtual size_t _fillBuffer(uint8_t* buf, size_t maxLen) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
class AsyncProgmemResponse: public AsyncAbstractResponse {
|
class AsyncProgmemResponse : public AsyncAbstractResponse {
|
||||||
private:
|
private:
|
||||||
const uint8_t * _content;
|
const uint8_t* _content;
|
||||||
size_t _readLength;
|
size_t _readLength;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AsyncProgmemResponse(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback=nullptr);
|
AsyncProgmemResponse(int code, const char* contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr);
|
||||||
|
AsyncProgmemResponse(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr) : AsyncProgmemResponse(code, contentType.c_str(), content, len, callback) {}
|
||||||
bool _sourceValid() const { return true; }
|
bool _sourceValid() const { return true; }
|
||||||
virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override;
|
virtual size_t _fillBuffer(uint8_t* buf, size_t maxLen) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
class cbuf;
|
class cbuf;
|
||||||
|
|
||||||
class AsyncResponseStream: public AsyncAbstractResponse, public Print {
|
class AsyncResponseStream : public AsyncAbstractResponse, public Print {
|
||||||
private:
|
private:
|
||||||
cbuf *_content;
|
std::unique_ptr<cbuf> _content;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AsyncResponseStream(const String& contentType, size_t bufferSize);
|
AsyncResponseStream(const char* contentType, size_t bufferSize);
|
||||||
|
AsyncResponseStream(const String& contentType, size_t bufferSize) : AsyncResponseStream(contentType.c_str(), bufferSize) {}
|
||||||
~AsyncResponseStream();
|
~AsyncResponseStream();
|
||||||
bool _sourceValid() const { return (_state < RESPONSE_END); }
|
bool _sourceValid() const { return (_state < RESPONSE_END); }
|
||||||
virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override;
|
virtual size_t _fillBuffer(uint8_t* buf, size_t maxLen) override;
|
||||||
size_t write(const uint8_t *data, size_t len);
|
size_t write(const uint8_t* data, size_t len);
|
||||||
size_t write(uint8_t data);
|
size_t write(uint8_t data);
|
||||||
using Print::write;
|
using Print::write;
|
||||||
};
|
};
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -21,110 +21,144 @@
|
|||||||
#include "ESPAsyncWebServer.h"
|
#include "ESPAsyncWebServer.h"
|
||||||
#include "WebHandlerImpl.h"
|
#include "WebHandlerImpl.h"
|
||||||
|
|
||||||
bool ON_STA_FILTER(AsyncWebServerRequest *request) {
|
using namespace asyncsrv;
|
||||||
|
|
||||||
|
bool ON_STA_FILTER(AsyncWebServerRequest* request) {
|
||||||
|
#ifndef CONFIG_IDF_TARGET_ESP32H2
|
||||||
return WiFi.localIP() == request->client()->localIP();
|
return WiFi.localIP() == request->client()->localIP();
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ON_AP_FILTER(AsyncWebServerRequest *request) {
|
bool ON_AP_FILTER(AsyncWebServerRequest* request) {
|
||||||
|
#ifndef CONFIG_IDF_TARGET_ESP32H2
|
||||||
return WiFi.localIP() != request->client()->localIP();
|
return WiFi.localIP() != request->client()->localIP();
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#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)
|
AsyncWebServer::AsyncWebServer(uint16_t port)
|
||||||
: _server(port)
|
: _server(port) {
|
||||||
, _rewrites(LinkedList<AsyncWebRewrite*>(nullptr))
|
|
||||||
, _handlers(LinkedList<AsyncWebHandler*>(nullptr))
|
|
||||||
{
|
|
||||||
_catchAllHandler = new AsyncCallbackWebHandler();
|
_catchAllHandler = new AsyncCallbackWebHandler();
|
||||||
if(_catchAllHandler == NULL)
|
if (_catchAllHandler == NULL)
|
||||||
return;
|
return;
|
||||||
_server.onClient([](void *s, AsyncClient* c){
|
_server.onClient([](void* s, AsyncClient* c) {
|
||||||
if(c == NULL)
|
if (c == NULL)
|
||||||
return;
|
return;
|
||||||
c->setRxTimeout(3);
|
c->setRxTimeout(3);
|
||||||
AsyncWebServerRequest *r = new AsyncWebServerRequest((AsyncWebServer*)s, c);
|
AsyncWebServerRequest* r = new AsyncWebServerRequest((AsyncWebServer*)s, c);
|
||||||
if(r == NULL){
|
if (r == NULL) {
|
||||||
c->close(true);
|
c->close(true);
|
||||||
c->free();
|
c->free();
|
||||||
delete c;
|
delete c;
|
||||||
}
|
}
|
||||||
}, this);
|
},
|
||||||
|
this);
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncWebServer::~AsyncWebServer(){
|
AsyncWebServer::~AsyncWebServer() {
|
||||||
reset();
|
reset();
|
||||||
end();
|
end();
|
||||||
if(_catchAllHandler) delete _catchAllHandler;
|
if (_catchAllHandler)
|
||||||
|
delete _catchAllHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncWebRewrite& AsyncWebServer::addRewrite(AsyncWebRewrite* rewrite){
|
AsyncWebRewrite& AsyncWebServer::addRewrite(std::shared_ptr<AsyncWebRewrite> rewrite) {
|
||||||
_rewrites.add(rewrite);
|
_rewrites.emplace_back(rewrite);
|
||||||
return *rewrite;
|
return *_rewrites.back().get();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AsyncWebServer::removeRewrite(AsyncWebRewrite *rewrite){
|
AsyncWebRewrite& AsyncWebServer::addRewrite(AsyncWebRewrite* rewrite) {
|
||||||
return _rewrites.remove(rewrite);
|
_rewrites.emplace_back(rewrite);
|
||||||
|
return *_rewrites.back().get();
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncWebRewrite& AsyncWebServer::rewrite(const char* from, const char* to){
|
bool AsyncWebServer::removeRewrite(AsyncWebRewrite* rewrite) {
|
||||||
return addRewrite(new AsyncWebRewrite(from, to));
|
return removeRewrite(rewrite->from().c_str(), rewrite->toUrl().c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncWebHandler& AsyncWebServer::addHandler(AsyncWebHandler* handler){
|
bool AsyncWebServer::removeRewrite(const char* from, const char* to) {
|
||||||
_handlers.add(handler);
|
for (auto r = _rewrites.begin(); r != _rewrites.end(); ++r) {
|
||||||
return *handler;
|
if (r->get()->from() == from && r->get()->toUrl() == to) {
|
||||||
|
_rewrites.erase(r);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AsyncWebServer::removeHandler(AsyncWebHandler *handler){
|
AsyncWebRewrite& AsyncWebServer::rewrite(const char* from, const char* to) {
|
||||||
return _handlers.remove(handler);
|
_rewrites.emplace_back(std::make_shared<AsyncWebRewrite>(from, to));
|
||||||
|
return *_rewrites.back().get();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AsyncWebServer::begin(){
|
AsyncWebHandler& AsyncWebServer::addHandler(AsyncWebHandler* handler) {
|
||||||
|
_handlers.emplace_back(handler);
|
||||||
|
return *(_handlers.back().get());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AsyncWebServer::removeHandler(AsyncWebHandler* handler) {
|
||||||
|
for (auto i = _handlers.begin(); i != _handlers.end(); ++i) {
|
||||||
|
if (i->get() == handler) {
|
||||||
|
_handlers.erase(i);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncWebServer::begin() {
|
||||||
_server.setNoDelay(true);
|
_server.setNoDelay(true);
|
||||||
_server.begin();
|
_server.begin();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AsyncWebServer::end(){
|
void AsyncWebServer::end() {
|
||||||
_server.end();
|
_server.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
#if ASYNC_TCP_SSL_ENABLED
|
#if ASYNC_TCP_SSL_ENABLED
|
||||||
void AsyncWebServer::onSslFileRequest(AcSSlFileHandler cb, void* arg){
|
void AsyncWebServer::onSslFileRequest(AcSSlFileHandler cb, void* arg) {
|
||||||
_server.onSslFileRequest(cb, arg);
|
_server.onSslFileRequest(cb, arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AsyncWebServer::beginSecure(const char *cert, const char *key, const char *password){
|
void AsyncWebServer::beginSecure(const char* cert, const char* key, const char* password) {
|
||||||
_server.beginSecure(cert, key, password);
|
_server.beginSecure(cert, key, password);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void AsyncWebServer::_handleDisconnect(AsyncWebServerRequest *request){
|
void AsyncWebServer::_handleDisconnect(AsyncWebServerRequest* request) {
|
||||||
delete request;
|
delete request;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AsyncWebServer::_rewriteRequest(AsyncWebServerRequest *request){
|
void AsyncWebServer::_rewriteRequest(AsyncWebServerRequest* request) {
|
||||||
for(const auto& r: _rewrites){
|
for (const auto& r : _rewrites) {
|
||||||
if (r->match(request)){
|
if (r->match(request)) {
|
||||||
request->_url = r->toUrl();
|
request->_url = r->toUrl();
|
||||||
request->_addGetParams(r->params());
|
request->_addGetParams(r->params());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AsyncWebServer::_attachHandler(AsyncWebServerRequest *request){
|
void AsyncWebServer::_attachHandler(AsyncWebServerRequest* request) {
|
||||||
for(const auto& h: _handlers){
|
for (auto& h : _handlers) {
|
||||||
if (h->filter(request) && h->canHandle(request)){
|
if (h->filter(request) && h->canHandle(request)) {
|
||||||
request->setHandler(h);
|
request->setHandler(h.get());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
request->addInterestingHeader("ANY");
|
|
||||||
request->setHandler(_catchAllHandler);
|
request->setHandler(_catchAllHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody) {
|
||||||
AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody){
|
|
||||||
AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler();
|
AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler();
|
||||||
handler->setUri(uri);
|
handler->setUri(uri);
|
||||||
handler->setMethod(method);
|
handler->setMethod(method);
|
||||||
@ -135,59 +169,31 @@ AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodCom
|
|||||||
return *handler;
|
return *handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload){
|
AsyncStaticWebHandler& AsyncWebServer::serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_control) {
|
||||||
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);
|
AsyncStaticWebHandler* handler = new AsyncStaticWebHandler(uri, fs, path, cache_control);
|
||||||
addHandler(handler);
|
addHandler(handler);
|
||||||
return *handler;
|
return *handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AsyncWebServer::onNotFound(ArRequestHandlerFunction fn){
|
void AsyncWebServer::onNotFound(ArRequestHandlerFunction fn) {
|
||||||
_catchAllHandler->onRequest(fn);
|
_catchAllHandler->onRequest(fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AsyncWebServer::onFileUpload(ArUploadHandlerFunction fn){
|
void AsyncWebServer::onFileUpload(ArUploadHandlerFunction fn) {
|
||||||
_catchAllHandler->onUpload(fn);
|
_catchAllHandler->onUpload(fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AsyncWebServer::onRequestBody(ArBodyHandlerFunction fn){
|
void AsyncWebServer::onRequestBody(ArBodyHandlerFunction fn) {
|
||||||
_catchAllHandler->onBody(fn);
|
_catchAllHandler->onBody(fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AsyncWebServer::reset(){
|
void AsyncWebServer::reset() {
|
||||||
_rewrites.free();
|
_rewrites.clear();
|
||||||
_handlers.free();
|
_handlers.clear();
|
||||||
|
|
||||||
if (_catchAllHandler != NULL){
|
if (_catchAllHandler != NULL) {
|
||||||
_catchAllHandler->onRequest(NULL);
|
_catchAllHandler->onRequest(NULL);
|
||||||
_catchAllHandler->onUpload(NULL);
|
_catchAllHandler->onUpload(NULL);
|
||||||
_catchAllHandler->onBody(NULL);
|
_catchAllHandler->onBody(NULL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
347
src/literals.h
Normal file
347
src/literals.h
Normal file
@ -0,0 +1,347 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace asyncsrv {
|
||||||
|
|
||||||
|
static constexpr const char* empty = "";
|
||||||
|
|
||||||
|
#ifndef ESP8622
|
||||||
|
static constexpr const char* T_100_CONTINUE = "100-continue";
|
||||||
|
static constexpr const char* T_ACCEPT = "Accept";
|
||||||
|
static constexpr const char* T_Accept_Ranges = "Accept-Ranges";
|
||||||
|
static constexpr const char* T_app_xform_urlencoded = "application/x-www-form-urlencoded";
|
||||||
|
static constexpr const char* T_AUTH = "Authorization";
|
||||||
|
static constexpr const char* T_BASIC = "Basic";
|
||||||
|
static constexpr const char* T_BASIC_REALM = "Basic realm=\"";
|
||||||
|
static constexpr const char* T_LOGIN_REQ = "Login Required";
|
||||||
|
static constexpr const char* T_BODY = "body";
|
||||||
|
static constexpr const char* T_Cache_Control = "Cache-Control";
|
||||||
|
static constexpr const char* T_chunked = "chunked";
|
||||||
|
static constexpr const char* T_close = "close";
|
||||||
|
static constexpr const char* T_Connection = "Connection";
|
||||||
|
static constexpr const char* T_Content_Disposition = "Content-Disposition";
|
||||||
|
static constexpr const char* T_Content_Encoding = "Content-Encoding";
|
||||||
|
static constexpr const char* T_Content_Length = "Content-Length";
|
||||||
|
static constexpr const char* T_Content_Type = "Content-Type";
|
||||||
|
static constexpr const char* T_Cookie = "Cookie";
|
||||||
|
static constexpr const char* T_DIGEST = "Digest";
|
||||||
|
static constexpr const char* T_DIGEST_ = "Digest ";
|
||||||
|
static constexpr const char* T_BEARER = "Bearer";
|
||||||
|
static constexpr const char* T_ETag = "ETag";
|
||||||
|
static constexpr const char* T_EXPECT = "Expect";
|
||||||
|
static constexpr const char* T_HTTP_1_0 = "HTTP/1.0";
|
||||||
|
static constexpr const char* T_HTTP_100_CONT = "HTTP/1.1 100 Continue\r\n\r\n";
|
||||||
|
static constexpr const char* T_IMS = "If-Modified-Since";
|
||||||
|
static constexpr const char* T_INM = "If-None-Match";
|
||||||
|
static constexpr const char* T_keep_alive = "keep-alive";
|
||||||
|
static constexpr const char* T_Last_Event_ID = "Last-Event-ID";
|
||||||
|
static constexpr const char* T_Last_Modified = "Last-Modified";
|
||||||
|
static constexpr const char* T_LOCATION = "Location";
|
||||||
|
static constexpr const char* T_MULTIPART_ = "multipart/";
|
||||||
|
static constexpr const char* T_no_cache = "no-cache";
|
||||||
|
static constexpr const char* T_none = "none";
|
||||||
|
static constexpr const char* T_UPGRADE = "Upgrade";
|
||||||
|
static constexpr const char* T_WS = "websocket";
|
||||||
|
static constexpr const char* T_WWW_AUTH = "WWW-Authenticate";
|
||||||
|
static constexpr const char* T_Transfer_Encoding = "Transfer-Encoding";
|
||||||
|
|
||||||
|
// HTTP Methods
|
||||||
|
static constexpr const char* T_ANY = "ANY";
|
||||||
|
static constexpr const char* T_GET = "GET";
|
||||||
|
static constexpr const char* T_POST = "POST";
|
||||||
|
static constexpr const char* T_PUT = "PUT";
|
||||||
|
static constexpr const char* T_DELETE = "DELETE";
|
||||||
|
static constexpr const char* T_PATCH = "PATCH";
|
||||||
|
static constexpr const char* T_HEAD = "HEAD";
|
||||||
|
static constexpr const char* T_OPTIONS = "OPTIONS";
|
||||||
|
static constexpr const char* T_UNKNOWN = "UNKNOWN";
|
||||||
|
|
||||||
|
// Req content types
|
||||||
|
static constexpr const char* T_RCT_NOT_USED = "RCT_NOT_USED";
|
||||||
|
static constexpr const char* T_RCT_DEFAULT = "RCT_DEFAULT";
|
||||||
|
static constexpr const char* T_RCT_HTTP = "RCT_HTTP";
|
||||||
|
static constexpr const char* T_RCT_WS = "RCT_WS";
|
||||||
|
static constexpr const char* T_RCT_EVENT = "RCT_EVENT";
|
||||||
|
static constexpr const char* T_ERROR = "ERROR";
|
||||||
|
|
||||||
|
// extentions & MIME-Types
|
||||||
|
static constexpr const char* T__css = ".css";
|
||||||
|
static constexpr const char* T__eot = ".eot";
|
||||||
|
static constexpr const char* T__gif = ".gif";
|
||||||
|
static constexpr const char* T__gz = ".gz";
|
||||||
|
static constexpr const char* T__htm = ".htm";
|
||||||
|
static constexpr const char* T__html = ".html";
|
||||||
|
static constexpr const char* T__ico = ".ico";
|
||||||
|
static constexpr const char* T__jpg = ".jpg";
|
||||||
|
static constexpr const char* T__js = ".js";
|
||||||
|
static constexpr const char* T__json = ".json";
|
||||||
|
static constexpr const char* T__pdf = ".pdf";
|
||||||
|
static constexpr const char* T__png = ".png";
|
||||||
|
static constexpr const char* T__svg = ".svg";
|
||||||
|
static constexpr const char* T__ttf = ".ttf";
|
||||||
|
static constexpr const char* T__woff = ".woff";
|
||||||
|
static constexpr const char* T__woff2 = ".woff2";
|
||||||
|
static constexpr const char* T__xml = ".xml";
|
||||||
|
static constexpr const char* T__zip = ".zip";
|
||||||
|
static constexpr const char* T_application_javascript = "application/javascript";
|
||||||
|
static constexpr const char* T_application_json = "application/json";
|
||||||
|
static constexpr const char* T_application_msgpack = "application/msgpack";
|
||||||
|
static constexpr const char* T_application_pdf = "application/pdf";
|
||||||
|
static constexpr const char* T_application_x_gzip = "application/x-gzip";
|
||||||
|
static constexpr const char* T_application_zip = "application/zip";
|
||||||
|
static constexpr const char* T_font_eot = "font/eot";
|
||||||
|
static constexpr const char* T_font_ttf = "font/ttf";
|
||||||
|
static constexpr const char* T_font_woff = "font/woff";
|
||||||
|
static constexpr const char* T_font_woff2 = "font/woff2";
|
||||||
|
static constexpr const char* T_image_gif = "image/gif";
|
||||||
|
static constexpr const char* T_image_jpeg = "image/jpeg";
|
||||||
|
static constexpr const char* T_image_png = "image/png";
|
||||||
|
static constexpr const char* T_image_svg_xml = "image/svg+xml";
|
||||||
|
static constexpr const char* T_image_x_icon = "image/x-icon";
|
||||||
|
static constexpr const char* T_text_css = "text/css";
|
||||||
|
static constexpr const char* T_text_event_stream = "text/event-stream";
|
||||||
|
static constexpr const char* T_text_html = "text/html";
|
||||||
|
static constexpr const char* T_text_plain = "text/plain";
|
||||||
|
static constexpr const char* T_text_xml = "text/xml";
|
||||||
|
|
||||||
|
// Responce codes
|
||||||
|
static constexpr const char* T_HTTP_CODE_100 = "Continue";
|
||||||
|
static constexpr const char* T_HTTP_CODE_101 = "Switching Protocols";
|
||||||
|
static constexpr const char* T_HTTP_CODE_200 = "OK";
|
||||||
|
static constexpr const char* T_HTTP_CODE_201 = "Created";
|
||||||
|
static constexpr const char* T_HTTP_CODE_202 = "Accepted";
|
||||||
|
static constexpr const char* T_HTTP_CODE_203 = "Non-Authoritative Information";
|
||||||
|
static constexpr const char* T_HTTP_CODE_204 = "No Content";
|
||||||
|
static constexpr const char* T_HTTP_CODE_205 = "Reset Content";
|
||||||
|
static constexpr const char* T_HTTP_CODE_206 = "Partial Content";
|
||||||
|
static constexpr const char* T_HTTP_CODE_300 = "Multiple Choices";
|
||||||
|
static constexpr const char* T_HTTP_CODE_301 = "Moved Permanently";
|
||||||
|
static constexpr const char* T_HTTP_CODE_302 = "Found";
|
||||||
|
static constexpr const char* T_HTTP_CODE_303 = "See Other";
|
||||||
|
static constexpr const char* T_HTTP_CODE_304 = "Not Modified";
|
||||||
|
static constexpr const char* T_HTTP_CODE_305 = "Use Proxy";
|
||||||
|
static constexpr const char* T_HTTP_CODE_307 = "Temporary Redirect";
|
||||||
|
static constexpr const char* T_HTTP_CODE_400 = "Bad Request";
|
||||||
|
static constexpr const char* T_HTTP_CODE_401 = "Unauthorized";
|
||||||
|
static constexpr const char* T_HTTP_CODE_402 = "Payment Required";
|
||||||
|
static constexpr const char* T_HTTP_CODE_403 = "Forbidden";
|
||||||
|
static constexpr const char* T_HTTP_CODE_404 = "Not Found";
|
||||||
|
static constexpr const char* T_HTTP_CODE_405 = "Method Not Allowed";
|
||||||
|
static constexpr const char* T_HTTP_CODE_406 = "Not Acceptable";
|
||||||
|
static constexpr const char* T_HTTP_CODE_407 = "Proxy Authentication Required";
|
||||||
|
static constexpr const char* T_HTTP_CODE_408 = "Request Time-out";
|
||||||
|
static constexpr const char* T_HTTP_CODE_409 = "Conflict";
|
||||||
|
static constexpr const char* T_HTTP_CODE_410 = "Gone";
|
||||||
|
static constexpr const char* T_HTTP_CODE_411 = "Length Required";
|
||||||
|
static constexpr const char* T_HTTP_CODE_412 = "Precondition Failed";
|
||||||
|
static constexpr const char* T_HTTP_CODE_413 = "Request Entity Too Large";
|
||||||
|
static constexpr const char* T_HTTP_CODE_414 = "Request-URI Too Large";
|
||||||
|
static constexpr const char* T_HTTP_CODE_415 = "Unsupported Media Type";
|
||||||
|
static constexpr const char* T_HTTP_CODE_416 = "Requested range not satisfiable";
|
||||||
|
static constexpr const char* T_HTTP_CODE_417 = "Expectation Failed";
|
||||||
|
static constexpr const char* T_HTTP_CODE_429 = "Too Many Requests";
|
||||||
|
static constexpr const char* T_HTTP_CODE_500 = "Internal Server Error";
|
||||||
|
static constexpr const char* T_HTTP_CODE_501 = "Not Implemented";
|
||||||
|
static constexpr const char* T_HTTP_CODE_502 = "Bad Gateway";
|
||||||
|
static constexpr const char* T_HTTP_CODE_503 = "Service Unavailable";
|
||||||
|
static constexpr const char* T_HTTP_CODE_504 = "Gateway Time-out";
|
||||||
|
static constexpr const char* T_HTTP_CODE_505 = "HTTP Version not supported";
|
||||||
|
static constexpr const char* T_HTTP_CODE_ANY = "Unknown code";
|
||||||
|
|
||||||
|
// other
|
||||||
|
static constexpr const char* T__opaque = "\", opaque=\"";
|
||||||
|
static constexpr const char* T_13 = "13";
|
||||||
|
static constexpr const char* T_auth_nonce = "\", qop=\"auth\", nonce=\"";
|
||||||
|
static constexpr const char* T_cnonce = "cnonce";
|
||||||
|
static constexpr const char* T_data_ = "data: ";
|
||||||
|
static constexpr const char* T_event_ = "event: ";
|
||||||
|
static constexpr const char* T_filename = "filename";
|
||||||
|
static constexpr const char* T_gzip = "gzip";
|
||||||
|
static constexpr const char* T_Host = "Host";
|
||||||
|
static constexpr const char* T_id__ = "id: ";
|
||||||
|
static constexpr const char* T_name = "name";
|
||||||
|
static constexpr const char* T_nc = "nc";
|
||||||
|
static constexpr const char* T_nonce = "nonce";
|
||||||
|
static constexpr const char* T_opaque = "opaque";
|
||||||
|
static constexpr const char* T_qop = "qop";
|
||||||
|
static constexpr const char* T_realm = "realm";
|
||||||
|
static constexpr const char* T_realm__ = "realm=\"";
|
||||||
|
static constexpr const char* T_response = "response";
|
||||||
|
static constexpr const char* T_retry_ = "retry: ";
|
||||||
|
static constexpr const char* T_rn = "\r\n";
|
||||||
|
static constexpr const char* T_rnrn = "\r\n\r\n";
|
||||||
|
static constexpr const char* T_uri = "uri";
|
||||||
|
static constexpr const char* T_username = "username";
|
||||||
|
|
||||||
|
|
||||||
|
#else // ESP8622
|
||||||
|
|
||||||
|
static const char T_100_CONTINUE[] PROGMEM = "100-continue";
|
||||||
|
static const char T_ACCEPT[] PROGMEM = "Accept";
|
||||||
|
static const char T_Accept_Ranges[] PROGMEM = "Accept-Ranges";
|
||||||
|
static const char T_app_xform_urlencoded[] PROGMEM = "application/x-www-form-urlencoded";
|
||||||
|
static const char T_AUTH[] PROGMEM = "Authorization";
|
||||||
|
static const char T_BASIC[] PROGMEM = "Basic";
|
||||||
|
static const char T_BASIC_REALM[] PROGMEM = "Basic realm=\"";
|
||||||
|
static const char T_LOGIN_REQ[] PROGMEM = "Login Required";
|
||||||
|
static const char T_BODY[] PROGMEM = "body";
|
||||||
|
static const char T_Cache_Control[] PROGMEM = "Cache-Control";
|
||||||
|
static const char T_chunked[] PROGMEM = "chunked";
|
||||||
|
static const char T_close[] PROGMEM = "close";
|
||||||
|
static const char T_Connection[] PROGMEM = "Connection";
|
||||||
|
static const char T_Content_Disposition[] PROGMEM = "Content-Disposition";
|
||||||
|
static const char T_Content_Encoding[] PROGMEM = "Content-Encoding";
|
||||||
|
static const char T_Content_Length[] PROGMEM = "Content-Length";
|
||||||
|
static const char T_Content_Type[] PROGMEM = "Content-Type";
|
||||||
|
static const char T_Cookie[] PROGMEM = "Cookie";
|
||||||
|
static const char T_DIGEST[] PROGMEM = "Digest";
|
||||||
|
static const char T_DIGEST_[] PROGMEM = "Digest ";
|
||||||
|
static const char T_BEARER[] PROGMEM = "Bearer";
|
||||||
|
static const char T_ETag[] PROGMEM = "ETag";
|
||||||
|
static const char T_EXPECT[] PROGMEM = "Expect";
|
||||||
|
static const char T_HTTP_1_0[] PROGMEM = "HTTP/1.0";
|
||||||
|
static const char T_HTTP_100_CONT[] PROGMEM = "HTTP/1.1 100 Continue\r\n\r\n";
|
||||||
|
static const char T_IMS[] PROGMEM = "If-Modified-Since";
|
||||||
|
static const char T_INM[] PROGMEM = "If-None-Match";
|
||||||
|
static const char T_keep_alive[] PROGMEM = "keep-alive";
|
||||||
|
static const char T_Last_Event_ID[] PROGMEM = "Last-Event-ID";
|
||||||
|
static const char T_Last_Modified[] PROGMEM = "Last-Modified";
|
||||||
|
static const char T_LOCATION[] PROGMEM = "Location";
|
||||||
|
static const char T_MULTIPART_[] PROGMEM = "multipart/";
|
||||||
|
static const char T_no_cache[] PROGMEM = "no-cache";
|
||||||
|
static const char T_none[] PROGMEM = "none";
|
||||||
|
static const char T_UPGRADE[] PROGMEM = "Upgrade";
|
||||||
|
static const char T_WS[] PROGMEM = "websocket";
|
||||||
|
static const char T_WWW_AUTH[] PROGMEM = "WWW-Authenticate";
|
||||||
|
static const char T_Transfer_Encoding[] PROGMEM = "Transfer-Encoding";
|
||||||
|
|
||||||
|
// HTTP Methods
|
||||||
|
static const char T_ANY[] PROGMEM = "ANY";
|
||||||
|
static const char T_GET[] PROGMEM = "GET";
|
||||||
|
static const char T_POST[] PROGMEM = "POST";
|
||||||
|
static const char T_PUT[] PROGMEM = "PUT";
|
||||||
|
static const char T_DELETE[] PROGMEM = "DELETE";
|
||||||
|
static const char T_PATCH[] PROGMEM = "PATCH";
|
||||||
|
static const char T_HEAD[] PROGMEM = "HEAD";
|
||||||
|
static const char T_OPTIONS[] PROGMEM = "OPTIONS";
|
||||||
|
static const char T_UNKNOWN[] PROGMEM = "UNKNOWN";
|
||||||
|
|
||||||
|
// Req content types
|
||||||
|
static const char T_RCT_NOT_USED[] PROGMEM = "RCT_NOT_USED";
|
||||||
|
static const char T_RCT_DEFAULT[] PROGMEM = "RCT_DEFAULT";
|
||||||
|
static const char T_RCT_HTTP[] PROGMEM = "RCT_HTTP";
|
||||||
|
static const char T_RCT_WS[] PROGMEM = "RCT_WS";
|
||||||
|
static const char T_RCT_EVENT[] PROGMEM = "RCT_EVENT";
|
||||||
|
static const char T_ERROR[] PROGMEM = "ERROR";
|
||||||
|
|
||||||
|
// extentions & MIME-Types
|
||||||
|
static const char T__css[] PROGMEM = ".css";
|
||||||
|
static const char T__eot[] PROGMEM = ".eot";
|
||||||
|
static const char T__gif[] PROGMEM = ".gif";
|
||||||
|
static const char T__gz[] PROGMEM = ".gz";
|
||||||
|
static const char T__htm[] PROGMEM = ".htm";
|
||||||
|
static const char T__html[] PROGMEM = ".html";
|
||||||
|
static const char T__ico[] PROGMEM = ".ico";
|
||||||
|
static const char T__jpg[] PROGMEM = ".jpg";
|
||||||
|
static const char T__js[] PROGMEM = ".js";
|
||||||
|
static const char T__json[] PROGMEM = ".json";
|
||||||
|
static const char T__pdf[] PROGMEM = ".pdf";
|
||||||
|
static const char T__png[] PROGMEM = ".png";
|
||||||
|
static const char T__svg[] PROGMEM = ".svg";
|
||||||
|
static const char T__ttf[] PROGMEM = ".ttf";
|
||||||
|
static const char T__woff[] PROGMEM = ".woff";
|
||||||
|
static const char T__woff2[] PROGMEM = ".woff2";
|
||||||
|
static const char T__xml[] PROGMEM = ".xml";
|
||||||
|
static const char T__zip[] PROGMEM = ".zip";
|
||||||
|
static const char T_application_javascript[] PROGMEM = "application/javascript";
|
||||||
|
static const char T_application_json[] PROGMEM = "application/json";
|
||||||
|
static const char T_application_msgpack[] PROGMEM = "application/msgpack";
|
||||||
|
static const char T_application_pdf[] PROGMEM = "application/pdf";
|
||||||
|
static const char T_application_x_gzip[] PROGMEM = "application/x-gzip";
|
||||||
|
static const char T_application_zip[] PROGMEM = "application/zip";
|
||||||
|
static const char T_font_eot[] PROGMEM = "font/eot";
|
||||||
|
static const char T_font_ttf[] PROGMEM = "font/ttf";
|
||||||
|
static const char T_font_woff[] PROGMEM = "font/woff";
|
||||||
|
static const char T_font_woff2[] PROGMEM = "font/woff2";
|
||||||
|
static const char T_image_gif[] PROGMEM = "image/gif";
|
||||||
|
static const char T_image_jpeg[] PROGMEM = "image/jpeg";
|
||||||
|
static const char T_image_png[] PROGMEM = "image/png";
|
||||||
|
static const char T_image_svg_xml[] PROGMEM = "image/svg+xml";
|
||||||
|
static const char T_image_x_icon[] PROGMEM = "image/x-icon";
|
||||||
|
static const char T_text_css[] PROGMEM = "text/css";
|
||||||
|
static const char T_text_event_stream[] PROGMEM = "text/event-stream";
|
||||||
|
static const char T_text_html[] PROGMEM = "text/html";
|
||||||
|
static const char T_text_plain[] PROGMEM = "text/plain";
|
||||||
|
static const char T_text_xml[] PROGMEM = "text/xml";
|
||||||
|
|
||||||
|
// Responce codes
|
||||||
|
static const char T_HTTP_CODE_100[] PROGMEM = "Continue";
|
||||||
|
static const char T_HTTP_CODE_101[] PROGMEM = "Switching Protocols";
|
||||||
|
static const char T_HTTP_CODE_200[] PROGMEM = "OK";
|
||||||
|
static const char T_HTTP_CODE_201[] PROGMEM = "Created";
|
||||||
|
static const char T_HTTP_CODE_202[] PROGMEM = "Accepted";
|
||||||
|
static const char T_HTTP_CODE_203[] PROGMEM = "Non-Authoritative Information";
|
||||||
|
static const char T_HTTP_CODE_204[] PROGMEM = "No Content";
|
||||||
|
static const char T_HTTP_CODE_205[] PROGMEM = "Reset Content";
|
||||||
|
static const char T_HTTP_CODE_206[] PROGMEM = "Partial Content";
|
||||||
|
static const char T_HTTP_CODE_300[] PROGMEM = "Multiple Choices";
|
||||||
|
static const char T_HTTP_CODE_301[] PROGMEM = "Moved Permanently";
|
||||||
|
static const char T_HTTP_CODE_302[] PROGMEM = "Found";
|
||||||
|
static const char T_HTTP_CODE_303[] PROGMEM = "See Other";
|
||||||
|
static const char T_HTTP_CODE_304[] PROGMEM = "Not Modified";
|
||||||
|
static const char T_HTTP_CODE_305[] PROGMEM = "Use Proxy";
|
||||||
|
static const char T_HTTP_CODE_307[] PROGMEM = "Temporary Redirect";
|
||||||
|
static const char T_HTTP_CODE_400[] PROGMEM = "Bad Request";
|
||||||
|
static const char T_HTTP_CODE_401[] PROGMEM = "Unauthorized";
|
||||||
|
static const char T_HTTP_CODE_402[] PROGMEM = "Payment Required";
|
||||||
|
static const char T_HTTP_CODE_403[] PROGMEM = "Forbidden";
|
||||||
|
static const char T_HTTP_CODE_404[] PROGMEM = "Not Found";
|
||||||
|
static const char T_HTTP_CODE_405[] PROGMEM = "Method Not Allowed";
|
||||||
|
static const char T_HTTP_CODE_406[] PROGMEM = "Not Acceptable";
|
||||||
|
static const char T_HTTP_CODE_407[] PROGMEM = "Proxy Authentication Required";
|
||||||
|
static const char T_HTTP_CODE_408[] PROGMEM = "Request Time-out";
|
||||||
|
static const char T_HTTP_CODE_409[] PROGMEM = "Conflict";
|
||||||
|
static const char T_HTTP_CODE_410[] PROGMEM = "Gone";
|
||||||
|
static const char T_HTTP_CODE_411[] PROGMEM = "Length Required";
|
||||||
|
static const char T_HTTP_CODE_412[] PROGMEM = "Precondition Failed";
|
||||||
|
static const char T_HTTP_CODE_413[] PROGMEM = "Request Entity Too Large";
|
||||||
|
static const char T_HTTP_CODE_414[] PROGMEM = "Request-URI Too Large";
|
||||||
|
static const char T_HTTP_CODE_415[] PROGMEM = "Unsupported Media Type";
|
||||||
|
static const char T_HTTP_CODE_416[] PROGMEM = "Requested range not satisfiable";
|
||||||
|
static const char T_HTTP_CODE_417[] PROGMEM = "Expectation Failed";
|
||||||
|
static const char T_HTTP_CODE_429[] PROGMEM = "Too Many Requests";
|
||||||
|
static const char T_HTTP_CODE_500[] PROGMEM = "Internal Server Error";
|
||||||
|
static const char T_HTTP_CODE_501[] PROGMEM = "Not Implemented";
|
||||||
|
static const char T_HTTP_CODE_502[] PROGMEM = "Bad Gateway";
|
||||||
|
static const char T_HTTP_CODE_503[] PROGMEM = "Service Unavailable";
|
||||||
|
static const char T_HTTP_CODE_504[] PROGMEM = "Gateway Time-out";
|
||||||
|
static const char T_HTTP_CODE_505[] PROGMEM = "HTTP Version not supported";
|
||||||
|
static const char T_HTTP_CODE_ANY[] PROGMEM = "Unknown code";
|
||||||
|
|
||||||
|
// other
|
||||||
|
static const char T__opaque[] PROGMEM = "\", opaque=\"";
|
||||||
|
static const char T_13[] PROGMEM = "13";
|
||||||
|
static const char T_auth_nonce[] PROGMEM = "\", qop=\"auth\", nonce=\"";
|
||||||
|
static const char T_cnonce[] PROGMEM = "cnonce";
|
||||||
|
static const char T_data_[] PROGMEM = "data: ";
|
||||||
|
static const char T_event_[] PROGMEM = "event: ";
|
||||||
|
static const char T_filename[] PROGMEM = "filename";
|
||||||
|
static const char T_gzip[] PROGMEM = "gzip";
|
||||||
|
static const char T_Host[] PROGMEM = "Host";
|
||||||
|
static const char T_id__[] PROGMEM = "id: ";
|
||||||
|
static const char T_name[] PROGMEM = "name";
|
||||||
|
static const char T_nc[] PROGMEM = "nc";
|
||||||
|
static const char T_nonce[] PROGMEM = "nonce";
|
||||||
|
static const char T_opaque[] PROGMEM = "opaque";
|
||||||
|
static const char T_qop[] PROGMEM = "qop";
|
||||||
|
static const char T_realm[] PROGMEM = "realm";
|
||||||
|
static const char T_realm__[] PROGMEM = "realm=\"";
|
||||||
|
static const char T_response[] PROGMEM = "response";
|
||||||
|
static const char T_retry_[] PROGMEM = "retry: ";
|
||||||
|
static const char T_rn[] PROGMEM = "\r\n";
|
||||||
|
static const char T_rnrn[] PROGMEM = "\r\n\r\n";
|
||||||
|
static const char T_uri[] PROGMEM = "uri";
|
||||||
|
static const char T_username[] PROGMEM = "username";
|
||||||
|
|
||||||
|
#endif // ESP8622
|
||||||
|
|
||||||
|
} // namespace asyncsrv {}
|
Loading…
Reference in New Issue
Block a user