mirror of
https://github.com/justcallmekoko/ESP32Marauder.git
synced 2025-12-05 20:40:25 -08:00
Add fixed ESPAsyncWebServer update NimBLE
This commit is contained in:
4
.github/workflows/build_push.yml
vendored
4
.github/workflows/build_push.yml
vendored
@@ -86,7 +86,7 @@ jobs:
|
|||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
repository: h2zero/NimBLE-Arduino
|
repository: h2zero/NimBLE-Arduino
|
||||||
ref: 1.2.0
|
ref: 1.4.2
|
||||||
path: CustomNimBLE-Arduino
|
path: CustomNimBLE-Arduino
|
||||||
|
|
||||||
- name: Install Adafruit_NeoPixel
|
- name: Install Adafruit_NeoPixel
|
||||||
@@ -167,7 +167,7 @@ jobs:
|
|||||||
- name: Modify platform.txt
|
- name: Modify platform.txt
|
||||||
run: |
|
run: |
|
||||||
for i in $(find /home/runner/.arduino15/packages/esp32/ -name "ld_libs"); do
|
for i in $(find /home/runner/.arduino15/packages/esp32/ -name "ld_libs"); do
|
||||||
sed -i 's/-lxtensa/-lxtensa -zmuldefs /' "$i"
|
sed -i 's/-lxtensa /-lxtensa -zmuldefs /' "$i"
|
||||||
cat "$i"
|
cat "$i"
|
||||||
done
|
done
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ extern "C" int ieee80211_raw_frame_sanity_check(int32_t arg, int32_t arg2, int32
|
|||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
uint8_t esp_base_mac_addr[6];
|
uint8_t esp_base_mac_addr[6];
|
||||||
|
esp_err_t esp_base_mac_addr_set(const uint8_t *addr);
|
||||||
esp_err_t esp_ble_gap_set_rand_addr(const uint8_t *rand_addr);
|
esp_err_t esp_ble_gap_set_rand_addr(const uint8_t *rand_addr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
165
libraries/ESPAsyncWebServer/LICENSE
Normal file
165
libraries/ESPAsyncWebServer/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.
|
||||||
87
libraries/ESPAsyncWebServer/README.md
Normal file
87
libraries/ESPAsyncWebServer/README.md
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
# ESP Async WebServer
|
||||||
|
|
||||||
|
[](https://opensource.org/license/lgpl-3-0/)
|
||||||
|
[](https://github.com/mathieucarbou/ESPAsyncWebServer/actions/workflows/ci.yml)
|
||||||
|
[](https://registry.platformio.org/libraries/mathieucarbou/ESP%20Async%20WebServer)
|
||||||
|
|
||||||
|
Asynchronous HTTP and WebSocket Server Library for ESP32.
|
||||||
|
Supports: WebSocket, SSE, Authentication, Arduino Json 7, File Upload, Static File serving, URL Rewrite, URL Redirect, etc.
|
||||||
|
|
||||||
|
This fork is based on [yubox-node-org/ESPAsyncWebServer](https://github.com/yubox-node-org/ESPAsyncWebServer) and includes all the concurrency fixes.
|
||||||
|
|
||||||
|
## Changes in this fork
|
||||||
|
|
||||||
|
- Removed SPIFFSEditor
|
||||||
|
- Deployed in PlatformIO registry and Arduino IDE library manager
|
||||||
|
- CI
|
||||||
|
- Some code cleanup
|
||||||
|
- `SSE_MAX_QUEUED_MESSAGES` to control the maximum number of messages that can be queued for a SSE client
|
||||||
|
- `write()` function public in `AsyncEventSource.h`
|
||||||
|
- Arduino Json 7 compatibility and backward compatible with 6 and 6 (changes in `AsyncJson.h`). The API to use Json has not changed. These are only internal changes.
|
||||||
|
- `WS_MAX_QUEUED_MESSAGES`: control the maximum number of messages that can be queued for a Websocket client
|
||||||
|
- Resurrected `AsyncWebSocketMessageBuffer` and `makeBuffer()` in order to make the fork API-compatible with the original library from me-no-dev regarding WebSocket.
|
||||||
|
- [#5](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/5) ([@vortigont](https://github.com/vortigont)): set real "Last-Modified" header based on file's LastWrite time
|
||||||
|
- [#13](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/13) ([@tueddy](https://github.com/tueddy)): Compile with Arduino 3 (ESP-IDF 5.1)
|
||||||
|
- [#14](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/14) ([@nilo85](https://github.com/nilo85)): Add support for Auth & GET requests in AsyncCallbackJsonWebHandler
|
||||||
|
- Added `setAuthentication(const String& username, const String& password)`
|
||||||
|
- Added `StreamConcat` example to shoiw how to stream multiple files in one response
|
||||||
|
- Remove filename after inline in Content-Disposition header according to RFC2183
|
||||||
|
- Depends on `mathieucarbou/Async TCP @ ^3.1.4`
|
||||||
|
- Arduino 3 / ESP-IDF 5.1 compatibility
|
||||||
|
- Added all flavors of `binary()`, `text()`, `binaryAll()` and `textAll()` in `AsyncWebSocket`
|
||||||
|
- Added `setCloseClientOnQueueFull(bool)` which can be set on a client to either close the connection or discard messages but not close the connection when the queue is full
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
Usage and API stays the same as the original library.
|
||||||
|
Please look at the original libraries for more examples and documentation.
|
||||||
|
|
||||||
|
[https://github.com/yubox-node-org/ESPAsyncWebServer](https://github.com/yubox-node-org/ESPAsyncWebServer)
|
||||||
|
|
||||||
|
## `AsyncWebSocketMessageBuffer` and `makeBuffer()`
|
||||||
|
|
||||||
|
The fork from `yubox-node-org` introduces some breaking API changes compared to the original library, especially regarding the use of `std::shared_ptr<std::vector<uint8_t>>` for WebSocket.
|
||||||
|
|
||||||
|
This fork is compatible with the original library from `me-no-dev` regarding WebSocket, and wraps the optimizations done by `yubox-node-org` in the `AsyncWebSocketMessageBuffer` class.
|
||||||
|
So you have the choice of which API to use.
|
||||||
|
I strongly suggest to use the optimized API from `yubox-node-org` as it is much more efficient.
|
||||||
|
|
||||||
|
Here is an example for serializing a Json document in a websocket message buffer. This code is compatible with any forks, but not optimized:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void send(JsonDocument& doc) {
|
||||||
|
const size_t len = measureJson(doc);
|
||||||
|
|
||||||
|
// original API from me-no-dev
|
||||||
|
AsyncWebSocketMessageBuffer* buffer = _ws->makeBuffer(len);
|
||||||
|
assert(buffer); // up to you to keep or remove this
|
||||||
|
serializeJson(doc, buffer->get(), len);
|
||||||
|
_ws->textAll(buffer);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Here is an example for serializing a Json document in a more optimized way, and compatible with both forks:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void send(JsonDocument& doc) {
|
||||||
|
const size_t len = measureJson(doc);
|
||||||
|
|
||||||
|
#if defined(ASYNCWEBSERVER_FORK_mathieucarbou)
|
||||||
|
|
||||||
|
// this fork (originally from yubox-node-org), uses another API with shared pointer that better support concurrent use cases then the original project
|
||||||
|
auto buffer = std::make_shared<std::vector<uint8_t>>(len);
|
||||||
|
assert(buffer); // up to you to keep or remove this
|
||||||
|
serializeJson(doc, buffer->data(), len);
|
||||||
|
_ws->textAll(std::move(buffer));
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
// original API from me-no-dev
|
||||||
|
AsyncWebSocketMessageBuffer* buffer = _ws->makeBuffer(len);
|
||||||
|
assert(buffer); // up to you to keep or remove this
|
||||||
|
serializeJson(doc, buffer->get(), len);
|
||||||
|
_ws->textAll(buffer);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
```
|
||||||
8
libraries/ESPAsyncWebServer/docs/_config.yml
Normal file
8
libraries/ESPAsyncWebServer/docs/_config.yml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# bundle exec jekyll serve --host=0.0.0.0
|
||||||
|
|
||||||
|
title: ESP Async WebServer
|
||||||
|
description: "Asynchronous HTTP and WebSocket Server Library for ESP32"
|
||||||
|
remote_theme: pages-themes/cayman@v0.2.0
|
||||||
|
plugins:
|
||||||
|
- jekyll-remote-theme
|
||||||
|
|
||||||
87
libraries/ESPAsyncWebServer/docs/index.md
Normal file
87
libraries/ESPAsyncWebServer/docs/index.md
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
# ESP Async WebServer
|
||||||
|
|
||||||
|
[](https://opensource.org/license/lgpl-3-0/)
|
||||||
|
[](https://github.com/mathieucarbou/ESPAsyncWebServer/actions/workflows/ci.yml)
|
||||||
|
[](https://registry.platformio.org/libraries/mathieucarbou/ESP%20Async%20WebServer)
|
||||||
|
|
||||||
|
Asynchronous HTTP and WebSocket Server Library for ESP32.
|
||||||
|
Supports: WebSocket, SSE, Authentication, Arduino Json 7, File Upload, Static File serving, URL Rewrite, URL Redirect, etc.
|
||||||
|
|
||||||
|
This fork is based on [yubox-node-org/ESPAsyncWebServer](https://github.com/yubox-node-org/ESPAsyncWebServer) and includes all the concurrency fixes.
|
||||||
|
|
||||||
|
## Changes in this fork
|
||||||
|
|
||||||
|
- Removed SPIFFSEditor
|
||||||
|
- Deployed in PlatformIO registry and Arduino IDE library manager
|
||||||
|
- CI
|
||||||
|
- Some code cleanup
|
||||||
|
- `SSE_MAX_QUEUED_MESSAGES` to control the maximum number of messages that can be queued for a SSE client
|
||||||
|
- `write()` function public in `AsyncEventSource.h`
|
||||||
|
- Arduino Json 7 compatibility and backward compatible with 6 and 6 (changes in `AsyncJson.h`). The API to use Json has not changed. These are only internal changes.
|
||||||
|
- `WS_MAX_QUEUED_MESSAGES`: control the maximum number of messages that can be queued for a Websocket client
|
||||||
|
- Resurrected `AsyncWebSocketMessageBuffer` and `makeBuffer()` in order to make the fork API-compatible with the original library from me-no-dev regarding WebSocket.
|
||||||
|
- [#5](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/5) ([@vortigont](https://github.com/vortigont)): set real "Last-Modified" header based on file's LastWrite time
|
||||||
|
- [#13](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/13) ([@tueddy](https://github.com/tueddy)): Compile with Arduino 3 (ESP-IDF 5.1)
|
||||||
|
- [#14](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/14) ([@nilo85](https://github.com/nilo85)): Add support for Auth & GET requests in AsyncCallbackJsonWebHandler
|
||||||
|
- Added `setAuthentication(const String& username, const String& password)`
|
||||||
|
- Added `StreamConcat` example to shoiw how to stream multiple files in one response
|
||||||
|
- Remove filename after inline in Content-Disposition header according to RFC2183
|
||||||
|
- Depends on `mathieucarbou/Async TCP @ ^3.1.4`
|
||||||
|
- Arduino 3 / ESP-IDF 5.1 compatibility
|
||||||
|
- Added all flavors of `binary()`, `text()`, `binaryAll()` and `textAll()` in `AsyncWebSocket`
|
||||||
|
- Added `setCloseClientOnQueueFull(bool)` which can be set on a client to either close the connection or discard messages but not close the connection when the queue is full
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
Usage and API stays the same as the original library.
|
||||||
|
Please look at the original libraries for more examples and documentation.
|
||||||
|
|
||||||
|
[https://github.com/yubox-node-org/ESPAsyncWebServer](https://github.com/yubox-node-org/ESPAsyncWebServer)
|
||||||
|
|
||||||
|
## `AsyncWebSocketMessageBuffer` and `makeBuffer()`
|
||||||
|
|
||||||
|
The fork from `yubox-node-org` introduces some breaking API changes compared to the original library, especially regarding the use of `std::shared_ptr<std::vector<uint8_t>>` for WebSocket.
|
||||||
|
|
||||||
|
This fork is compatible with the original library from `me-no-dev` regarding WebSocket, and wraps the optimizations done by `yubox-node-org` in the `AsyncWebSocketMessageBuffer` class.
|
||||||
|
So you have the choice of which API to use.
|
||||||
|
I strongly suggest to use the optimized API from `yubox-node-org` as it is much more efficient.
|
||||||
|
|
||||||
|
Here is an example for serializing a Json document in a websocket message buffer. This code is compatible with any forks, but not optimized:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void send(JsonDocument& doc) {
|
||||||
|
const size_t len = measureJson(doc);
|
||||||
|
|
||||||
|
// original API from me-no-dev
|
||||||
|
AsyncWebSocketMessageBuffer* buffer = _ws->makeBuffer(len);
|
||||||
|
assert(buffer); // up to you to keep or remove this
|
||||||
|
serializeJson(doc, buffer->get(), len);
|
||||||
|
_ws->textAll(buffer);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Here is an example for serializing a Json document in a more optimized way, and compatible with both forks:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void send(JsonDocument& doc) {
|
||||||
|
const size_t len = measureJson(doc);
|
||||||
|
|
||||||
|
#if defined(ASYNCWEBSERVER_FORK_mathieucarbou)
|
||||||
|
|
||||||
|
// this fork (originally from yubox-node-org), uses another API with shared pointer that better support concurrent use cases then the original project
|
||||||
|
auto buffer = std::make_shared<std::vector<uint8_t>>(len);
|
||||||
|
assert(buffer); // up to you to keep or remove this
|
||||||
|
serializeJson(doc, buffer->data(), len);
|
||||||
|
_ws->textAll(std::move(buffer));
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
// original API from me-no-dev
|
||||||
|
AsyncWebSocketMessageBuffer* buffer = _ws->makeBuffer(len);
|
||||||
|
assert(buffer); // up to you to keep or remove this
|
||||||
|
serializeJson(doc, buffer->get(), len);
|
||||||
|
_ws->textAll(buffer);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
#include <DNSServer.h>
|
||||||
|
#ifdef ESP32
|
||||||
|
#include <WiFi.h>
|
||||||
|
#include <AsyncTCP.h>
|
||||||
|
#elif defined(ESP8266)
|
||||||
|
#include <ESP8266WiFi.h>
|
||||||
|
#include <ESPAsyncTCP.h>
|
||||||
|
#endif
|
||||||
|
#include "ESPAsyncWebServer.h"
|
||||||
|
|
||||||
|
DNSServer dnsServer;
|
||||||
|
AsyncWebServer server(80);
|
||||||
|
|
||||||
|
class CaptiveRequestHandler : public AsyncWebHandler {
|
||||||
|
public:
|
||||||
|
CaptiveRequestHandler() {}
|
||||||
|
virtual ~CaptiveRequestHandler() {}
|
||||||
|
|
||||||
|
bool canHandle(__unused AsyncWebServerRequest *request){
|
||||||
|
//request->addInterestingHeader("ANY");
|
||||||
|
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());
|
||||||
|
response->printf("<p>Try opening <a href='http://%s'>this link</a> instead</p>", WiFi.softAPIP().toString().c_str());
|
||||||
|
response->print("</body></html>");
|
||||||
|
request->send(response);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
void setup(){
|
||||||
|
Serial.begin(115200);
|
||||||
|
Serial.println();
|
||||||
|
Serial.println("Configuring access point...");
|
||||||
|
|
||||||
|
if (!WiFi.softAP("esp-captive")) {
|
||||||
|
Serial.println("Soft AP creation failed.");
|
||||||
|
while (1);
|
||||||
|
}
|
||||||
|
|
||||||
|
dnsServer.start(53, "*", WiFi.softAPIP());
|
||||||
|
server.addHandler(new CaptiveRequestHandler()).setFilter(ON_AP_FILTER);//only when requested from AP
|
||||||
|
//more handlers...
|
||||||
|
server.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop(){
|
||||||
|
dnsServer.processNextRequest();
|
||||||
|
}
|
||||||
108
libraries/ESPAsyncWebServer/examples/Filters/Filters.ino
Normal file
108
libraries/ESPAsyncWebServer/examples/Filters/Filters.ino
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
// 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>
|
||||||
|
#endif
|
||||||
|
#include "ESPAsyncWebServer.h"
|
||||||
|
|
||||||
|
DNSServer dnsServer;
|
||||||
|
AsyncWebServer server(80);
|
||||||
|
|
||||||
|
class CaptiveRequestHandler : public AsyncWebHandler {
|
||||||
|
public:
|
||||||
|
CaptiveRequestHandler() {}
|
||||||
|
virtual ~CaptiveRequestHandler() {}
|
||||||
|
|
||||||
|
bool canHandle(__unused AsyncWebServerRequest* request) {
|
||||||
|
// request->addInterestingHeader("ANY");
|
||||||
|
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());
|
||||||
|
response->printf("<p>Try opening <a href='http://%s'>this link</a> instead</p>", WiFi.softAPIP().toString().c_str());
|
||||||
|
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...");
|
||||||
|
Serial.println("WiFi.localIP(): " + WiFi.localIP().toString());
|
||||||
|
Serial.println("request->client()->localIP(): " + request->client()->localIP().toString());
|
||||||
|
#if ESP_IDF_VERSION_MAJOR >= 5
|
||||||
|
Serial.println("WiFi.type(): " + String((int)WiFi.localIP().type()));
|
||||||
|
Serial.println("request->client()->type(): " + String((int)request->client()->localIP().type()));
|
||||||
|
#endif
|
||||||
|
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());
|
||||||
|
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...");
|
||||||
|
Serial.println("WiFi.localIP(): " + WiFi.localIP().toString());
|
||||||
|
Serial.println("request->client()->localIP(): " + request->client()->localIP().toString());
|
||||||
|
#if ESP_IDF_VERSION_MAJOR >= 5
|
||||||
|
Serial.println("WiFi.type(): " + String((int)WiFi.localIP().type()));
|
||||||
|
Serial.println("request->client()->type(): " + String((int)request->client()->localIP().type()));
|
||||||
|
#endif
|
||||||
|
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());
|
||||||
|
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();
|
||||||
|
|
||||||
|
WiFi.persistent(false);
|
||||||
|
WiFi.begin("IoT");
|
||||||
|
while (WiFi.status() != WL_CONNECTED) {
|
||||||
|
delay(500);
|
||||||
|
}
|
||||||
|
Serial.println("Connected to WiFi with IP address: " + WiFi.localIP().toString());
|
||||||
|
server.begin();
|
||||||
|
|
||||||
|
// while (!hit2) {
|
||||||
|
// delay(10);
|
||||||
|
// }
|
||||||
|
// delay(1000); // Wait for the client to process the response
|
||||||
|
// ESP.restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
//
|
||||||
|
// 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() {
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
#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();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t readBytes(char* buffer, size_t length) override {
|
||||||
|
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;
|
||||||
|
};
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
#include <Arduino.h>
|
||||||
|
#include <DNSServer.h>
|
||||||
|
#ifdef ESP32
|
||||||
|
#include <AsyncTCP.h>
|
||||||
|
#include <WiFi.h>
|
||||||
|
#elif defined(ESP8266)
|
||||||
|
#include <ESP8266WiFi.h>
|
||||||
|
#include <ESPAsyncTCP.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();
|
||||||
|
|
||||||
|
WiFi.mode(WIFI_AP);
|
||||||
|
WiFi.softAP("esp-captive");
|
||||||
|
dnsServer.start(53, "*", WiFi.softAPIP());
|
||||||
|
|
||||||
|
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;
|
||||||
|
content.printf("FreeHeap: %" PRIu32, ESP.getFreeHeap());
|
||||||
|
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) {
|
||||||
|
Serial.printf("FreeHeap: %" PRIu32, ESP.getFreeHeap());
|
||||||
|
last = millis();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
#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;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t readBytes(char* buffer, size_t length) override {
|
||||||
|
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;
|
||||||
|
};
|
||||||
54
libraries/ESPAsyncWebServer/library.json
Normal file
54
libraries/ESPAsyncWebServer/library.json
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
{
|
||||||
|
"name": "ESP Async WebServer",
|
||||||
|
"version": "2.10.4",
|
||||||
|
"description": "Asynchronous HTTP and WebSocket Server Library for ESP32. 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"
|
||||||
|
],
|
||||||
|
"dependencies": [
|
||||||
|
{
|
||||||
|
"owner": "mathieucarbou",
|
||||||
|
"name": "Async TCP",
|
||||||
|
"version": "^3.1.4",
|
||||||
|
"platforms": "espressif32"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"owner": "esphome",
|
||||||
|
"name": "ESPAsyncTCP-esphome",
|
||||||
|
"version": "^2.0.0",
|
||||||
|
"platforms": "espressif8266"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Hash",
|
||||||
|
"platforms": "espressif8266"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"export": {
|
||||||
|
"include": [
|
||||||
|
"examples",
|
||||||
|
"src",
|
||||||
|
"library.json",
|
||||||
|
"library.properties",
|
||||||
|
"LICENSE",
|
||||||
|
"README.md"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
10
libraries/ESPAsyncWebServer/library.properties
Normal file
10
libraries/ESPAsyncWebServer/library.properties
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
name=ESP Async WebServer
|
||||||
|
version=2.10.4
|
||||||
|
author=Me-No-Dev
|
||||||
|
maintainer=Mathieu Carbou <mathieu.carbou@gmail.com>
|
||||||
|
sentence=Asynchronous HTTP and WebSocket Server Library for ESP32
|
||||||
|
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=esp8266,esp32
|
||||||
|
license=LGPL-3.0
|
||||||
44
libraries/ESPAsyncWebServer/platformio.ini
Normal file
44
libraries/ESPAsyncWebServer/platformio.ini
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
[env]
|
||||||
|
framework = arduino
|
||||||
|
build_flags =
|
||||||
|
-Wall -Wextra
|
||||||
|
-D CONFIG_ARDUHAL_LOG_COLORS
|
||||||
|
-D CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_VERBOSE
|
||||||
|
lib_deps =
|
||||||
|
bblanchon/ArduinoJson @ 7.0.4
|
||||||
|
mathieucarbou/Async TCP @ ^3.1.4
|
||||||
|
; https://github.com/mathieucarbou/AsyncTCP
|
||||||
|
; https://github.com/me-no-dev/AsyncTCP
|
||||||
|
esphome/ESPAsyncTCP-esphome @ 2.0.0
|
||||||
|
upload_protocol = esptool
|
||||||
|
monitor_speed = 115200
|
||||||
|
monitor_filters = esp32_exception_decoder, log2file
|
||||||
|
|
||||||
|
[platformio]
|
||||||
|
lib_dir = .
|
||||||
|
; src_dir = examples/CaptivePortal
|
||||||
|
; src_dir = examples/SimpleServer
|
||||||
|
src_dir = examples/StreamFiles
|
||||||
|
; src_dir = examples/Filters
|
||||||
|
|
||||||
|
[env:arduino]
|
||||||
|
platform = espressif32
|
||||||
|
board = esp32dev
|
||||||
|
|
||||||
|
[env:arduino-2]
|
||||||
|
platform = espressif32@6.7.0
|
||||||
|
board = esp32dev
|
||||||
|
|
||||||
|
[env:arduino-3]
|
||||||
|
platform = espressif32
|
||||||
|
platform_packages=
|
||||||
|
platformio/framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#3.0.0
|
||||||
|
platformio/framework-arduinoespressif32-libs @ https://github.com/espressif/arduino-esp32/releases/download/3.0.0/esp32-arduino-libs-3.0.0.zip
|
||||||
|
board = esp32dev
|
||||||
|
|
||||||
|
[env:esp8266]
|
||||||
|
platform = espressif8266
|
||||||
|
board = huzzah
|
||||||
|
lib_deps =
|
||||||
|
bblanchon/ArduinoJson @ 7.0.4
|
||||||
|
esphome/ESPAsyncTCP-esphome @ 2.0.0
|
||||||
412
libraries/ESPAsyncWebServer/src/AsyncEventSource.cpp
Normal file
412
libraries/ESPAsyncWebServer/src/AsyncEventSource.cpp
Normal file
@@ -0,0 +1,412 @@
|
|||||||
|
/*
|
||||||
|
Asynchronous WebServer library for Espressif MCUs
|
||||||
|
|
||||||
|
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
#include "Arduino.h"
|
||||||
|
#include "AsyncEventSource.h"
|
||||||
|
#ifndef ESP8266
|
||||||
|
#include <rom/ets_sys.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static String generateEventMessage(const char *message, const char *event, uint32_t id, uint32_t reconnect){
|
||||||
|
String ev;
|
||||||
|
|
||||||
|
if(reconnect){
|
||||||
|
ev += F("retry: ");
|
||||||
|
ev += reconnect;
|
||||||
|
ev += F("\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(id){
|
||||||
|
ev += F("id: ");
|
||||||
|
ev += String(id);
|
||||||
|
ev += F("\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(event != NULL){
|
||||||
|
ev += F("event: ");
|
||||||
|
ev += String(event);
|
||||||
|
ev += F("\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(message != NULL){
|
||||||
|
size_t messageLen = strlen(message);
|
||||||
|
char * lineStart = (char *)message;
|
||||||
|
char * lineEnd;
|
||||||
|
do {
|
||||||
|
char * nextN = strchr(lineStart, '\n');
|
||||||
|
char * nextR = strchr(lineStart, '\r');
|
||||||
|
if(nextN == NULL && nextR == NULL){
|
||||||
|
size_t llen = ((char *)message + messageLen) - lineStart;
|
||||||
|
char * ldata = (char *)malloc(llen+1);
|
||||||
|
if(ldata != NULL){
|
||||||
|
memcpy(ldata, lineStart, llen);
|
||||||
|
ldata[llen] = 0;
|
||||||
|
ev += F("data: ");
|
||||||
|
ev += ldata;
|
||||||
|
ev += F("\r\n\r\n");
|
||||||
|
free(ldata);
|
||||||
|
}
|
||||||
|
lineStart = (char *)message + messageLen;
|
||||||
|
} else {
|
||||||
|
char * nextLine = NULL;
|
||||||
|
if(nextN != NULL && nextR != NULL){
|
||||||
|
if(nextR < nextN){
|
||||||
|
lineEnd = nextR;
|
||||||
|
if(nextN == (nextR + 1))
|
||||||
|
nextLine = nextN + 1;
|
||||||
|
else
|
||||||
|
nextLine = nextR + 1;
|
||||||
|
} else {
|
||||||
|
lineEnd = nextN;
|
||||||
|
if(nextR == (nextN + 1))
|
||||||
|
nextLine = nextR + 1;
|
||||||
|
else
|
||||||
|
nextLine = nextN + 1;
|
||||||
|
}
|
||||||
|
} else if(nextN != NULL){
|
||||||
|
lineEnd = nextN;
|
||||||
|
nextLine = nextN + 1;
|
||||||
|
} else {
|
||||||
|
lineEnd = nextR;
|
||||||
|
nextLine = nextR + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t llen = lineEnd - lineStart;
|
||||||
|
char * ldata = (char *)malloc(llen+1);
|
||||||
|
if(ldata != NULL){
|
||||||
|
memcpy(ldata, lineStart, llen);
|
||||||
|
ldata[llen] = 0;
|
||||||
|
ev += F("data: ");
|
||||||
|
ev += ldata;
|
||||||
|
ev += F("\r\n");
|
||||||
|
free(ldata);
|
||||||
|
}
|
||||||
|
lineStart = nextLine;
|
||||||
|
if(lineStart == ((char *)message + messageLen))
|
||||||
|
ev += F("\r\n");
|
||||||
|
}
|
||||||
|
} while(lineStart < ((char *)message + messageLen));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ev;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message
|
||||||
|
|
||||||
|
AsyncEventSourceMessage::AsyncEventSourceMessage(const char * data, size_t len)
|
||||||
|
: _data(nullptr), _len(len), _sent(0), _acked(0)
|
||||||
|
{
|
||||||
|
_data = (uint8_t*)malloc(_len+1);
|
||||||
|
if(_data == nullptr){
|
||||||
|
_len = 0;
|
||||||
|
} else {
|
||||||
|
memcpy(_data, data, len);
|
||||||
|
_data[_len] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncEventSourceMessage::~AsyncEventSourceMessage() {
|
||||||
|
if(_data != NULL)
|
||||||
|
free(_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t AsyncEventSourceMessage::ack(size_t len, uint32_t time) {
|
||||||
|
(void)time;
|
||||||
|
// If the whole message is now acked...
|
||||||
|
if(_acked + len > _len){
|
||||||
|
// Return the number of extra bytes acked (they will be carried on to the next message)
|
||||||
|
const size_t extra = _acked + len - _len;
|
||||||
|
_acked = _len;
|
||||||
|
return extra;
|
||||||
|
}
|
||||||
|
// Return that no extra bytes left.
|
||||||
|
_acked += len;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This could also return void as the return value is not used.
|
||||||
|
// Leaving as-is for compatibility...
|
||||||
|
size_t AsyncEventSourceMessage::send(AsyncClient *client) {
|
||||||
|
if (_sent >= _len) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const size_t len_to_send = _len - _sent;
|
||||||
|
auto position = reinterpret_cast<const char*>(_data + _sent);
|
||||||
|
const size_t sent_now = client->write(position, len_to_send);
|
||||||
|
_sent += sent_now;
|
||||||
|
return sent_now;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client
|
||||||
|
|
||||||
|
AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server)
|
||||||
|
: _messageQueue(AlternativeLinkedList<AsyncEventSourceMessage *>([](AsyncEventSourceMessage *m){ delete m; }))
|
||||||
|
{
|
||||||
|
_client = request->client();
|
||||||
|
_server = server;
|
||||||
|
_lastId = 0;
|
||||||
|
if(request->hasHeader(F("Last-Event-ID")))
|
||||||
|
_lastId = atoi(request->getHeader(F("Last-Event-ID"))->value().c_str());
|
||||||
|
|
||||||
|
_client->setRxTimeout(0);
|
||||||
|
_client->onError(NULL, NULL);
|
||||||
|
_client->onAck([](void *r, AsyncClient* c, size_t len, uint32_t time){ (void)c; ((AsyncEventSourceClient*)(r))->_onAck(len, time); }, this);
|
||||||
|
_client->onPoll([](void *r, AsyncClient* c){ (void)c; ((AsyncEventSourceClient*)(r))->_onPoll(); }, this);
|
||||||
|
_client->onData(NULL, NULL);
|
||||||
|
_client->onTimeout([this](void *r, AsyncClient* c __attribute__((unused)), uint32_t time){ ((AsyncEventSourceClient*)(r))->_onTimeout(time); }, this);
|
||||||
|
_client->onDisconnect([this](void *r, AsyncClient* c){ ((AsyncEventSourceClient*)(r))->_onDisconnect(); delete c; }, this);
|
||||||
|
|
||||||
|
_server->_addClient(this);
|
||||||
|
delete request;
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncEventSourceClient::~AsyncEventSourceClient(){
|
||||||
|
_lockmq.lock();
|
||||||
|
_messageQueue.free();
|
||||||
|
_lockmq.unlock();
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncEventSourceClient::_queueMessage(AsyncEventSourceMessage *dataMessage){
|
||||||
|
if(dataMessage == NULL)
|
||||||
|
return;
|
||||||
|
if(!connected()){
|
||||||
|
delete dataMessage;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//length() is not thread-safe, thus acquiring the lock before this call..
|
||||||
|
_lockmq.lock();
|
||||||
|
if(_messageQueue.length() >= SSE_MAX_QUEUED_MESSAGES){
|
||||||
|
#ifdef ESP8266
|
||||||
|
ets_printf(String(F("ERROR: Too many messages queued\n")).c_str());
|
||||||
|
#else
|
||||||
|
log_e("Too many messages queued: deleting message");
|
||||||
|
#endif
|
||||||
|
delete dataMessage;
|
||||||
|
} else {
|
||||||
|
_messageQueue.add(dataMessage);
|
||||||
|
// runqueue trigger when new messages added
|
||||||
|
if(_client->canSend()) {
|
||||||
|
_runQueue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_lockmq.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncEventSourceClient::_onAck(size_t len, uint32_t time){
|
||||||
|
// Same here, acquiring the lock early
|
||||||
|
_lockmq.lock();
|
||||||
|
while(len && !_messageQueue.isEmpty()){
|
||||||
|
len = _messageQueue.front()->ack(len, time);
|
||||||
|
if(_messageQueue.front()->finished())
|
||||||
|
_messageQueue.remove(_messageQueue.front());
|
||||||
|
}
|
||||||
|
_runQueue();
|
||||||
|
_lockmq.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncEventSourceClient::_onPoll(){
|
||||||
|
_lockmq.lock();
|
||||||
|
if(!_messageQueue.isEmpty()){
|
||||||
|
_runQueue();
|
||||||
|
}
|
||||||
|
_lockmq.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncEventSourceClient::_onTimeout(uint32_t time __attribute__((unused))){
|
||||||
|
_client->close(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncEventSourceClient::_onDisconnect(){
|
||||||
|
_client = NULL;
|
||||||
|
_server->_handleDisconnect(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncEventSourceClient::close(){
|
||||||
|
if(_client != NULL)
|
||||||
|
_client->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncEventSourceClient::write(const char * message, size_t len){
|
||||||
|
_queueMessage(new AsyncEventSourceMessage(message, len));
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncEventSourceClient::send(const char *message, const char *event, uint32_t id, uint32_t reconnect){
|
||||||
|
String ev = generateEventMessage(message, event, id, reconnect);
|
||||||
|
_queueMessage(new AsyncEventSourceMessage(ev.c_str(), ev.length()));
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t AsyncEventSourceClient::packetsWaiting() const {
|
||||||
|
size_t len;
|
||||||
|
_lockmq.lock();
|
||||||
|
len = _messageQueue.length();
|
||||||
|
_lockmq.unlock();
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncEventSourceClient::_runQueue() {
|
||||||
|
// Calls to this private method now already protected by _lockmq acquisition
|
||||||
|
// so no extra call of _lockmq.lock() here..
|
||||||
|
for (auto i = _messageQueue.begin(); i != _messageQueue.end(); ++i) {
|
||||||
|
// If it crashes here, iterator (i) has been invalidated as _messageQueue
|
||||||
|
// has been changed... (UL 2020-11-15: Not supposed to happen any more ;-) )
|
||||||
|
if (!(*i)->sent()) {
|
||||||
|
(*i)->send(_client);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Handler
|
||||||
|
|
||||||
|
AsyncEventSource::AsyncEventSource(const String& url)
|
||||||
|
: _url(url)
|
||||||
|
, _clients(AlternativeLinkedList<AsyncEventSourceClient *>([](AsyncEventSourceClient *c){ delete c; }))
|
||||||
|
, _connectcb(NULL)
|
||||||
|
{}
|
||||||
|
|
||||||
|
AsyncEventSource::~AsyncEventSource(){
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncEventSource::onConnect(ArEventHandlerFunction cb){
|
||||||
|
_connectcb = cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncEventSource::authorizeConnect(ArAuthorizeConnectHandler cb){
|
||||||
|
_authorizeConnectHandler = cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncEventSource::_addClient(AsyncEventSourceClient * client){
|
||||||
|
/*char * temp = (char *)malloc(2054);
|
||||||
|
if(temp != NULL){
|
||||||
|
memset(temp+1,' ',2048);
|
||||||
|
temp[0] = ':';
|
||||||
|
temp[2049] = '\r';
|
||||||
|
temp[2050] = '\n';
|
||||||
|
temp[2051] = '\r';
|
||||||
|
temp[2052] = '\n';
|
||||||
|
temp[2053] = 0;
|
||||||
|
client->write((const char *)temp, 2053);
|
||||||
|
free(temp);
|
||||||
|
}*/
|
||||||
|
AsyncWebLockGuard l(_client_queue_lock);
|
||||||
|
_clients.add(client);
|
||||||
|
if(_connectcb)
|
||||||
|
_connectcb(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncEventSource::_handleDisconnect(AsyncEventSourceClient * client){
|
||||||
|
AsyncWebLockGuard l(_client_queue_lock);
|
||||||
|
_clients.remove(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncEventSource::close(){
|
||||||
|
// While the whole loop is not done, the linked list is locked and so the
|
||||||
|
// iterator should remain valid even when AsyncEventSource::_handleDisconnect()
|
||||||
|
// is called very early
|
||||||
|
AsyncWebLockGuard l(_client_queue_lock);
|
||||||
|
for(const auto &c: _clients){
|
||||||
|
if(c->connected())
|
||||||
|
c->close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pmb fix
|
||||||
|
size_t AsyncEventSource::avgPacketsWaiting() const {
|
||||||
|
size_t aql = 0;
|
||||||
|
uint32_t nConnectedClients = 0;
|
||||||
|
AsyncWebLockGuard l(_client_queue_lock);
|
||||||
|
if (_clients.isEmpty()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
for(const auto &c: _clients){
|
||||||
|
if(c->connected()) {
|
||||||
|
aql += c->packetsWaiting();
|
||||||
|
++nConnectedClients;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ((aql) + (nConnectedClients/2)) / (nConnectedClients); // round up
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncEventSource::send(
|
||||||
|
const char *message, const char *event, uint32_t id, uint32_t reconnect){
|
||||||
|
String ev = generateEventMessage(message, event, id, reconnect);
|
||||||
|
AsyncWebLockGuard l(_client_queue_lock);
|
||||||
|
for(const auto &c: _clients){
|
||||||
|
if(c->connected()) {
|
||||||
|
c->write(ev.c_str(), ev.length());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t AsyncEventSource::count() const {
|
||||||
|
size_t n_clients;
|
||||||
|
AsyncWebLockGuard l(_client_queue_lock);
|
||||||
|
n_clients = _clients.count_if([](AsyncEventSourceClient *c){
|
||||||
|
return c->connected();
|
||||||
|
});
|
||||||
|
return n_clients;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AsyncEventSource::canHandle(AsyncWebServerRequest *request){
|
||||||
|
if(request->method() != HTTP_GET || !request->url().equals(_url)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
request->addInterestingHeader(F("Last-Event-ID"));
|
||||||
|
request->addInterestingHeader("Cookie");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncEventSource::handleRequest(AsyncWebServerRequest *request){
|
||||||
|
if((_username.length() && _password.length()) && !request->authenticate(_username.c_str(), _password.c_str())) {
|
||||||
|
return request->requestAuthentication();
|
||||||
|
}
|
||||||
|
if(_authorizeConnectHandler != NULL){
|
||||||
|
if(!_authorizeConnectHandler(request)){
|
||||||
|
return request->send(401);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
request->send(new AsyncEventSourceResponse(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response
|
||||||
|
|
||||||
|
AsyncEventSourceResponse::AsyncEventSourceResponse(AsyncEventSource *server){
|
||||||
|
_server = server;
|
||||||
|
_code = 200;
|
||||||
|
_contentType = F("text/event-stream");
|
||||||
|
_sendContentLength = false;
|
||||||
|
addHeader(F("Cache-Control"), F("no-cache"));
|
||||||
|
addHeader(F("Connection"), F("keep-alive"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncEventSourceResponse::_respond(AsyncWebServerRequest *request){
|
||||||
|
String out = _assembleHead(request->version());
|
||||||
|
request->client()->write(out.c_str(), _headLength);
|
||||||
|
_state = RESPONSE_WAIT_ACK;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t AsyncEventSourceResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time __attribute__((unused))){
|
||||||
|
if(len){
|
||||||
|
new AsyncEventSourceClient(request, _server);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
147
libraries/ESPAsyncWebServer/src/AsyncEventSource.h
Normal file
147
libraries/ESPAsyncWebServer/src/AsyncEventSource.h
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
/*
|
||||||
|
Asynchronous WebServer library for Espressif MCUs
|
||||||
|
|
||||||
|
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
#ifndef ASYNCEVENTSOURCE_H_
|
||||||
|
#define ASYNCEVENTSOURCE_H_
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#ifdef ESP32
|
||||||
|
#include <AsyncTCP.h>
|
||||||
|
#ifndef SSE_MAX_QUEUED_MESSAGES
|
||||||
|
#define SSE_MAX_QUEUED_MESSAGES 32
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
#include <ESPAsyncTCP.h>
|
||||||
|
#ifndef SSE_MAX_QUEUED_MESSAGES
|
||||||
|
#define SSE_MAX_QUEUED_MESSAGES 8
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <ESPAsyncWebServer.h>
|
||||||
|
|
||||||
|
#include "AsyncWebSynchronization.h"
|
||||||
|
|
||||||
|
#ifdef ESP8266
|
||||||
|
#include <Hash.h>
|
||||||
|
#ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library
|
||||||
|
#include <../src/Hash.h>
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef ESP32
|
||||||
|
#define DEFAULT_MAX_SSE_CLIENTS 8
|
||||||
|
#else
|
||||||
|
#define DEFAULT_MAX_SSE_CLIENTS 4
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class AsyncEventSource;
|
||||||
|
class AsyncEventSourceResponse;
|
||||||
|
class AsyncEventSourceClient;
|
||||||
|
typedef std::function<void(AsyncEventSourceClient *client)> ArEventHandlerFunction;
|
||||||
|
typedef std::function<bool(AsyncWebServerRequest *request)> ArAuthorizeConnectHandler;
|
||||||
|
|
||||||
|
class AsyncEventSourceMessage {
|
||||||
|
private:
|
||||||
|
uint8_t * _data;
|
||||||
|
size_t _len;
|
||||||
|
size_t _sent;
|
||||||
|
//size_t _ack;
|
||||||
|
size_t _acked;
|
||||||
|
public:
|
||||||
|
AsyncEventSourceMessage(const char * data, size_t len);
|
||||||
|
~AsyncEventSourceMessage();
|
||||||
|
size_t ack(size_t len, uint32_t time __attribute__((unused)));
|
||||||
|
size_t send(AsyncClient *client);
|
||||||
|
bool finished(){ return _acked == _len; }
|
||||||
|
bool sent() { return _sent == _len; }
|
||||||
|
};
|
||||||
|
|
||||||
|
class AsyncEventSourceClient {
|
||||||
|
private:
|
||||||
|
AsyncClient *_client;
|
||||||
|
AsyncEventSource *_server;
|
||||||
|
uint32_t _lastId;
|
||||||
|
AlternativeLinkedList<AsyncEventSourceMessage *> _messageQueue;
|
||||||
|
// ArFi 2020-08-27 for protecting/serializing _messageQueue
|
||||||
|
AsyncPlainLock _lockmq;
|
||||||
|
void _queueMessage(AsyncEventSourceMessage *dataMessage);
|
||||||
|
void _runQueue();
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server);
|
||||||
|
~AsyncEventSourceClient();
|
||||||
|
|
||||||
|
AsyncClient* client(){ return _client; }
|
||||||
|
void close();
|
||||||
|
void write(const char * message, size_t len);
|
||||||
|
void send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0);
|
||||||
|
bool connected() const { return (_client != NULL) && _client->connected(); }
|
||||||
|
uint32_t lastId() const { return _lastId; }
|
||||||
|
size_t packetsWaiting() const;
|
||||||
|
|
||||||
|
//system callbacks (do not call)
|
||||||
|
void _onAck(size_t len, uint32_t time);
|
||||||
|
void _onPoll();
|
||||||
|
void _onTimeout(uint32_t time);
|
||||||
|
void _onDisconnect();
|
||||||
|
};
|
||||||
|
|
||||||
|
class AsyncEventSource: public AsyncWebHandler {
|
||||||
|
private:
|
||||||
|
String _url;
|
||||||
|
AlternativeLinkedList<AsyncEventSourceClient *> _clients;
|
||||||
|
// Same as for individual messages, protect mutations of _clients list
|
||||||
|
// since simultaneous access from different tasks is possible
|
||||||
|
AsyncWebLock _client_queue_lock;
|
||||||
|
ArEventHandlerFunction _connectcb;
|
||||||
|
ArAuthorizeConnectHandler _authorizeConnectHandler;
|
||||||
|
public:
|
||||||
|
AsyncEventSource(const String& url);
|
||||||
|
~AsyncEventSource();
|
||||||
|
|
||||||
|
const char * url() const { return _url.c_str(); }
|
||||||
|
void close();
|
||||||
|
void onConnect(ArEventHandlerFunction cb);
|
||||||
|
void authorizeConnect(ArAuthorizeConnectHandler cb);
|
||||||
|
void send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0);
|
||||||
|
// number of clients connected
|
||||||
|
size_t count() const;
|
||||||
|
size_t avgPacketsWaiting() const;
|
||||||
|
|
||||||
|
//system callbacks (do not call)
|
||||||
|
void _addClient(AsyncEventSourceClient * client);
|
||||||
|
void _handleDisconnect(AsyncEventSourceClient * client);
|
||||||
|
virtual bool canHandle(AsyncWebServerRequest *request) override final;
|
||||||
|
virtual void handleRequest(AsyncWebServerRequest *request) override final;
|
||||||
|
};
|
||||||
|
|
||||||
|
class AsyncEventSourceResponse: public AsyncWebServerResponse {
|
||||||
|
private:
|
||||||
|
String _content;
|
||||||
|
AsyncEventSource *_server;
|
||||||
|
public:
|
||||||
|
AsyncEventSourceResponse(AsyncEventSource *server);
|
||||||
|
void _respond(AsyncWebServerRequest *request);
|
||||||
|
size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time);
|
||||||
|
bool _sourceValid() const { return true; }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* ASYNCEVENTSOURCE_H_ */
|
||||||
282
libraries/ESPAsyncWebServer/src/AsyncJson.h
Normal file
282
libraries/ESPAsyncWebServer/src/AsyncJson.h
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
// AsyncJson.h
|
||||||
|
/*
|
||||||
|
Async Response to use with ArduinoJson and AsyncWebServer
|
||||||
|
Written by Andrew Melvin (SticilFace) with help from me-no-dev and BBlanchon.
|
||||||
|
|
||||||
|
Example of callback in use
|
||||||
|
|
||||||
|
server.on("/json", HTTP_ANY, [](AsyncWebServerRequest * request) {
|
||||||
|
|
||||||
|
AsyncJsonResponse * response = new AsyncJsonResponse();
|
||||||
|
JsonObject& root = response->getRoot();
|
||||||
|
root["key1"] = "key number one";
|
||||||
|
JsonObject& nested = root.createNestedObject("nested");
|
||||||
|
nested["key1"] = "key number one";
|
||||||
|
|
||||||
|
response->setLength();
|
||||||
|
request->send(response);
|
||||||
|
});
|
||||||
|
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
Async Request to use with ArduinoJson and AsyncWebServer
|
||||||
|
Written by Arsène von Wyss (avonwyss)
|
||||||
|
|
||||||
|
Example
|
||||||
|
|
||||||
|
AsyncCallbackJsonWebHandler* handler = new AsyncCallbackJsonWebHandler("/rest/endpoint");
|
||||||
|
handler->onRequest([](AsyncWebServerRequest *request, JsonVariant &json) {
|
||||||
|
JsonObject& jsonObj = json.as<JsonObject>();
|
||||||
|
// ...
|
||||||
|
});
|
||||||
|
server.addHandler(handler);
|
||||||
|
|
||||||
|
*/
|
||||||
|
#ifndef ASYNC_JSON_H_
|
||||||
|
#define ASYNC_JSON_H_
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
#include <ESPAsyncWebServer.h>
|
||||||
|
#include <Print.h>
|
||||||
|
|
||||||
|
#if ARDUINOJSON_VERSION_MAJOR == 6
|
||||||
|
#ifndef DYNAMIC_JSON_DOCUMENT_SIZE
|
||||||
|
#define DYNAMIC_JSON_DOCUMENT_SIZE 1024
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
constexpr const char* JSON_MIMETYPE = "application/json";
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Json Response
|
||||||
|
* */
|
||||||
|
|
||||||
|
class ChunkPrint : public Print {
|
||||||
|
private:
|
||||||
|
uint8_t* _destination;
|
||||||
|
size_t _to_skip;
|
||||||
|
size_t _to_write;
|
||||||
|
size_t _pos;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ChunkPrint(uint8_t* destination, size_t from, size_t len)
|
||||||
|
: _destination(destination), _to_skip(from), _to_write(len), _pos{0} {}
|
||||||
|
virtual ~ChunkPrint() {}
|
||||||
|
size_t write(uint8_t c) {
|
||||||
|
if (_to_skip > 0) {
|
||||||
|
_to_skip--;
|
||||||
|
return 1;
|
||||||
|
} else if (_to_write > 0) {
|
||||||
|
_to_write--;
|
||||||
|
_destination[_pos++] = c;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
size_t write(const uint8_t* buffer, size_t size) {
|
||||||
|
return this->Print::write(buffer, size);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class AsyncJsonResponse : public AsyncAbstractResponse {
|
||||||
|
protected:
|
||||||
|
#if ARDUINOJSON_VERSION_MAJOR == 5
|
||||||
|
DynamicJsonBuffer _jsonBuffer;
|
||||||
|
#elif ARDUINOJSON_VERSION_MAJOR == 6
|
||||||
|
DynamicJsonDocument _jsonBuffer;
|
||||||
|
#else
|
||||||
|
JsonDocument _jsonBuffer;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
JsonVariant _root;
|
||||||
|
bool _isValid;
|
||||||
|
|
||||||
|
public:
|
||||||
|
#if ARDUINOJSON_VERSION_MAJOR == 5
|
||||||
|
AsyncJsonResponse(bool isArray = false) : _isValid{false} {
|
||||||
|
_code = 200;
|
||||||
|
_contentType = JSON_MIMETYPE;
|
||||||
|
if (isArray)
|
||||||
|
_root = _jsonBuffer.createArray();
|
||||||
|
else
|
||||||
|
_root = _jsonBuffer.createObject();
|
||||||
|
}
|
||||||
|
#elif ARDUINOJSON_VERSION_MAJOR == 6
|
||||||
|
AsyncJsonResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE) : _jsonBuffer(maxJsonBufferSize), _isValid{false} {
|
||||||
|
_code = 200;
|
||||||
|
_contentType = JSON_MIMETYPE;
|
||||||
|
if (isArray)
|
||||||
|
_root = _jsonBuffer.createNestedArray();
|
||||||
|
else
|
||||||
|
_root = _jsonBuffer.createNestedObject();
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
AsyncJsonResponse(bool isArray = false) : _isValid{false} {
|
||||||
|
_code = 200;
|
||||||
|
_contentType = JSON_MIMETYPE;
|
||||||
|
if (isArray)
|
||||||
|
_root = _jsonBuffer.add<JsonArray>();
|
||||||
|
else
|
||||||
|
_root = _jsonBuffer.add<JsonObject>();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
~AsyncJsonResponse() {}
|
||||||
|
JsonVariant& getRoot() { return _root; }
|
||||||
|
bool _sourceValid() const { return _isValid; }
|
||||||
|
size_t setLength() {
|
||||||
|
|
||||||
|
#if ARDUINOJSON_VERSION_MAJOR == 5
|
||||||
|
_contentLength = _root.measureLength();
|
||||||
|
#else
|
||||||
|
_contentLength = measureJson(_root);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (_contentLength) {
|
||||||
|
_isValid = true;
|
||||||
|
}
|
||||||
|
return _contentLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t getSize() const { return _jsonBuffer.size(); }
|
||||||
|
|
||||||
|
#if ARDUINOJSON_VERSION_MAJOR >= 6
|
||||||
|
bool overflowed() const { return _jsonBuffer.overflowed(); }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
size_t _fillBuffer(uint8_t* data, size_t len) {
|
||||||
|
ChunkPrint dest(data, _sentLength, len);
|
||||||
|
|
||||||
|
#if ARDUINOJSON_VERSION_MAJOR == 5
|
||||||
|
_root.printTo(dest);
|
||||||
|
#else
|
||||||
|
serializeJson(_root, dest);
|
||||||
|
#endif
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class PrettyAsyncJsonResponse : public AsyncJsonResponse {
|
||||||
|
public:
|
||||||
|
#if ARDUINOJSON_VERSION_MAJOR == 6
|
||||||
|
PrettyAsyncJsonResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE) : AsyncJsonResponse{isArray, maxJsonBufferSize} {}
|
||||||
|
#else
|
||||||
|
PrettyAsyncJsonResponse(bool isArray = false) : AsyncJsonResponse{isArray} {}
|
||||||
|
#endif
|
||||||
|
size_t setLength() {
|
||||||
|
#if ARDUINOJSON_VERSION_MAJOR == 5
|
||||||
|
_contentLength = _root.measurePrettyLength();
|
||||||
|
#else
|
||||||
|
_contentLength = measureJsonPretty(_root);
|
||||||
|
#endif
|
||||||
|
if (_contentLength) {
|
||||||
|
_isValid = true;
|
||||||
|
}
|
||||||
|
return _contentLength;
|
||||||
|
}
|
||||||
|
size_t _fillBuffer(uint8_t* data, size_t len) {
|
||||||
|
ChunkPrint dest(data, _sentLength, len);
|
||||||
|
#if ARDUINOJSON_VERSION_MAJOR == 5
|
||||||
|
_root.prettyPrintTo(dest);
|
||||||
|
#else
|
||||||
|
serializeJsonPretty(_root, dest);
|
||||||
|
#endif
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef std::function<void(AsyncWebServerRequest* request, JsonVariant& json)> ArJsonRequestHandlerFunction;
|
||||||
|
|
||||||
|
class AsyncCallbackJsonWebHandler : public AsyncWebHandler {
|
||||||
|
private:
|
||||||
|
protected:
|
||||||
|
const String _uri;
|
||||||
|
WebRequestMethodComposite _method;
|
||||||
|
ArJsonRequestHandlerFunction _onRequest;
|
||||||
|
size_t _contentLength;
|
||||||
|
#if ARDUINOJSON_VERSION_MAJOR == 6
|
||||||
|
const size_t maxJsonBufferSize;
|
||||||
|
#endif
|
||||||
|
size_t _maxContentLength;
|
||||||
|
|
||||||
|
public:
|
||||||
|
#if ARDUINOJSON_VERSION_MAJOR == 6
|
||||||
|
AsyncCallbackJsonWebHandler(const String& uri, ArJsonRequestHandlerFunction onRequest, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE)
|
||||||
|
: _uri(uri), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), maxJsonBufferSize(maxJsonBufferSize), _maxContentLength(16384) {}
|
||||||
|
#else
|
||||||
|
AsyncCallbackJsonWebHandler(const String& uri, ArJsonRequestHandlerFunction onRequest)
|
||||||
|
: _uri(uri), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), _maxContentLength(16384) {}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void setMethod(WebRequestMethodComposite method) { _method = method; }
|
||||||
|
void setMaxContentLength(int maxContentLength) { _maxContentLength = maxContentLength; }
|
||||||
|
void onRequest(ArJsonRequestHandlerFunction fn) { _onRequest = fn; }
|
||||||
|
|
||||||
|
virtual bool canHandle(AsyncWebServerRequest* request) override final {
|
||||||
|
if (!_onRequest)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
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(JSON_MIMETYPE))
|
||||||
|
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) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
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
|
||||||
1353
libraries/ESPAsyncWebServer/src/AsyncWebSocket.cpp
Normal file
1353
libraries/ESPAsyncWebServer/src/AsyncWebSocket.cpp
Normal file
File diff suppressed because it is too large
Load Diff
354
libraries/ESPAsyncWebServer/src/AsyncWebSocket.h
Normal file
354
libraries/ESPAsyncWebServer/src/AsyncWebSocket.h
Normal file
@@ -0,0 +1,354 @@
|
|||||||
|
/*
|
||||||
|
Asynchronous WebServer library for Espressif MCUs
|
||||||
|
|
||||||
|
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
|
||||||
|
This file is part of the esp8266 core for Arduino environment.
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
#ifndef ASYNCWEBSOCKET_H_
|
||||||
|
#define ASYNCWEBSOCKET_H_
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#ifdef ESP32
|
||||||
|
#include <AsyncTCP.h>
|
||||||
|
#ifndef WS_MAX_QUEUED_MESSAGES
|
||||||
|
#define WS_MAX_QUEUED_MESSAGES 32
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
#include <ESPAsyncTCP.h>
|
||||||
|
#ifndef WS_MAX_QUEUED_MESSAGES
|
||||||
|
#define WS_MAX_QUEUED_MESSAGES 8
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
#include <ESPAsyncWebServer.h>
|
||||||
|
|
||||||
|
#include "AsyncWebSynchronization.h"
|
||||||
|
|
||||||
|
#include <list>
|
||||||
|
#include <deque>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#ifdef ESP8266
|
||||||
|
#include <Hash.h>
|
||||||
|
#ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library
|
||||||
|
#include <../src/Hash.h>
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef ESP32
|
||||||
|
#define DEFAULT_MAX_WS_CLIENTS 8
|
||||||
|
#else
|
||||||
|
#define DEFAULT_MAX_WS_CLIENTS 4
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class AsyncWebSocket;
|
||||||
|
class AsyncWebSocketResponse;
|
||||||
|
class AsyncWebSocketClient;
|
||||||
|
class AsyncWebSocketControl;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
/** Message type as defined by enum AwsFrameType.
|
||||||
|
* Note: Applications will only see WS_TEXT and WS_BINARY.
|
||||||
|
* All other types are handled by the library. */
|
||||||
|
uint8_t message_opcode;
|
||||||
|
/** Frame number of a fragmented message. */
|
||||||
|
uint32_t num;
|
||||||
|
/** Is this the last frame in a fragmented message ?*/
|
||||||
|
uint8_t final;
|
||||||
|
/** Is this frame masked? */
|
||||||
|
uint8_t masked;
|
||||||
|
/** Message type as defined by enum AwsFrameType.
|
||||||
|
* This value is the same as message_opcode for non-fragmented
|
||||||
|
* messages, but may also be WS_CONTINUATION in a fragmented message. */
|
||||||
|
uint8_t opcode;
|
||||||
|
/** Length of the current frame.
|
||||||
|
* This equals the total length of the message if num == 0 && final == true */
|
||||||
|
uint64_t len;
|
||||||
|
/** Mask key */
|
||||||
|
uint8_t mask[4];
|
||||||
|
/** Offset of the data inside the current frame. */
|
||||||
|
uint64_t index;
|
||||||
|
} AwsFrameInfo;
|
||||||
|
|
||||||
|
typedef enum { WS_DISCONNECTED, WS_CONNECTED, WS_DISCONNECTING } AwsClientStatus;
|
||||||
|
typedef enum { WS_CONTINUATION, WS_TEXT, WS_BINARY, WS_DISCONNECT = 0x08, WS_PING, WS_PONG } AwsFrameType;
|
||||||
|
typedef enum { WS_MSG_SENDING, WS_MSG_SENT, WS_MSG_ERROR } AwsMessageStatus;
|
||||||
|
typedef enum { WS_EVT_CONNECT, WS_EVT_DISCONNECT, WS_EVT_PONG, WS_EVT_ERROR, WS_EVT_DATA } AwsEventType;
|
||||||
|
|
||||||
|
class AsyncWebSocketMessageBuffer {
|
||||||
|
friend AsyncWebSocket;
|
||||||
|
friend AsyncWebSocketClient;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<std::vector<uint8_t>> _buffer;
|
||||||
|
|
||||||
|
public:
|
||||||
|
AsyncWebSocketMessageBuffer();
|
||||||
|
AsyncWebSocketMessageBuffer(size_t size);
|
||||||
|
AsyncWebSocketMessageBuffer(uint8_t* data, size_t size);
|
||||||
|
~AsyncWebSocketMessageBuffer();
|
||||||
|
bool reserve(size_t size);
|
||||||
|
uint8_t* get() { return _buffer->data(); }
|
||||||
|
size_t length() const { return _buffer->size(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
class AsyncWebSocketMessage
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
std::shared_ptr<std::vector<uint8_t>> _WSbuffer;
|
||||||
|
uint8_t _opcode{WS_TEXT};
|
||||||
|
bool _mask{false};
|
||||||
|
AwsMessageStatus _status{WS_MSG_ERROR};
|
||||||
|
size_t _sent{};
|
||||||
|
size_t _ack{};
|
||||||
|
size_t _acked{};
|
||||||
|
|
||||||
|
public:
|
||||||
|
AsyncWebSocketMessage(std::shared_ptr<std::vector<uint8_t>> buffer, uint8_t opcode=WS_TEXT, bool mask=false);
|
||||||
|
|
||||||
|
bool finished() const { return _status != WS_MSG_SENDING; }
|
||||||
|
bool betweenFrames() const { return _acked == _ack; }
|
||||||
|
|
||||||
|
void ack(size_t len, uint32_t time);
|
||||||
|
size_t send(AsyncClient *client);
|
||||||
|
};
|
||||||
|
|
||||||
|
class AsyncWebSocketClient {
|
||||||
|
private:
|
||||||
|
AsyncClient *_client;
|
||||||
|
AsyncWebSocket *_server;
|
||||||
|
uint32_t _clientId;
|
||||||
|
AwsClientStatus _status;
|
||||||
|
|
||||||
|
AsyncWebLock _lock;
|
||||||
|
|
||||||
|
std::deque<AsyncWebSocketControl> _controlQueue;
|
||||||
|
std::deque<AsyncWebSocketMessage> _messageQueue;
|
||||||
|
bool closeWhenFull = true;
|
||||||
|
|
||||||
|
uint8_t _pstate;
|
||||||
|
AwsFrameInfo _pinfo;
|
||||||
|
|
||||||
|
uint32_t _lastMessageTime;
|
||||||
|
uint32_t _keepAlivePeriod;
|
||||||
|
|
||||||
|
void _queueControl(uint8_t opcode, const uint8_t *data=NULL, size_t len=0, bool mask=false);
|
||||||
|
void _queueMessage(std::shared_ptr<std::vector<uint8_t>> buffer, uint8_t opcode=WS_TEXT, bool mask=false);
|
||||||
|
void _runQueue();
|
||||||
|
void _clearQueue();
|
||||||
|
|
||||||
|
public:
|
||||||
|
void *_tempObject;
|
||||||
|
|
||||||
|
AsyncWebSocketClient(AsyncWebServerRequest *request, AsyncWebSocket *server);
|
||||||
|
~AsyncWebSocketClient();
|
||||||
|
|
||||||
|
//client id increments for the given server
|
||||||
|
uint32_t id() const { return _clientId; }
|
||||||
|
AwsClientStatus status() const { return _status; }
|
||||||
|
AsyncClient* client() { return _client; }
|
||||||
|
const AsyncClient* client() const { return _client; }
|
||||||
|
AsyncWebSocket *server(){ return _server; }
|
||||||
|
const AsyncWebSocket *server() const { return _server; }
|
||||||
|
AwsFrameInfo const &pinfo() const { return _pinfo; }
|
||||||
|
|
||||||
|
// - If "true" (default), the connection will be closed if the message queue is full.
|
||||||
|
// This is the default behavior in yubox-node-org, which is not silently discarding messages but instead closes the connection.
|
||||||
|
// The big issue with this behavior is that is can cause the UI to automatically re-create a new WS connection, which can be filled again,
|
||||||
|
// 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; }
|
||||||
|
|
||||||
|
IPAddress remoteIP() const;
|
||||||
|
uint16_t remotePort() const;
|
||||||
|
|
||||||
|
bool shouldBeDeleted() const { return !_client; }
|
||||||
|
|
||||||
|
//control frames
|
||||||
|
void close(uint16_t code=0, const char * message=NULL);
|
||||||
|
void ping(const uint8_t *data=NULL, size_t len=0);
|
||||||
|
|
||||||
|
//set auto-ping period in seconds. disabled if zero (default)
|
||||||
|
void keepAlivePeriod(uint16_t seconds){
|
||||||
|
_keepAlivePeriod = seconds * 1000;
|
||||||
|
}
|
||||||
|
uint16_t keepAlivePeriod(){
|
||||||
|
return (uint16_t)(_keepAlivePeriod / 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
//data packets
|
||||||
|
void message(std::shared_ptr<std::vector<uint8_t>> buffer, uint8_t opcode=WS_TEXT, bool mask=false) { _queueMessage(buffer, opcode, mask); }
|
||||||
|
bool queueIsFull() const;
|
||||||
|
size_t queueLen() const;
|
||||||
|
|
||||||
|
size_t printf(const char *format, ...) __attribute__ ((format (printf, 2, 3)));
|
||||||
|
#ifndef ESP32
|
||||||
|
size_t printf_P(PGM_P formatP, ...) __attribute__ ((format (printf, 2, 3)));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void text(std::shared_ptr<std::vector<uint8_t>> buffer);
|
||||||
|
void text(const uint8_t *message, size_t len);
|
||||||
|
void text(const char *message, size_t len);
|
||||||
|
void text(const char *message);
|
||||||
|
void text(const String &message);
|
||||||
|
void text(const __FlashStringHelper *message);
|
||||||
|
void text(AsyncWebSocketMessageBuffer *buffer);
|
||||||
|
|
||||||
|
void binary(std::shared_ptr<std::vector<uint8_t>> buffer);
|
||||||
|
void binary(const uint8_t *message, size_t len);
|
||||||
|
void binary(const char * message, size_t len);
|
||||||
|
void binary(const char * message);
|
||||||
|
void binary(const String &message);
|
||||||
|
void binary(const __FlashStringHelper *message, size_t len);
|
||||||
|
void binary(AsyncWebSocketMessageBuffer *buffer);
|
||||||
|
|
||||||
|
bool canSend() const;
|
||||||
|
|
||||||
|
//system callbacks (do not call)
|
||||||
|
void _onAck(size_t len, uint32_t time);
|
||||||
|
void _onError(int8_t);
|
||||||
|
void _onPoll();
|
||||||
|
void _onTimeout(uint32_t time);
|
||||||
|
void _onDisconnect();
|
||||||
|
void _onData(void *pbuf, size_t plen);
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef std::function<bool(AsyncWebServerRequest *request)> AwsHandshakeHandler;
|
||||||
|
typedef std::function<void(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len)> AwsEventHandler;
|
||||||
|
|
||||||
|
//WebServer Handler implementation that plays the role of a socket server
|
||||||
|
class AsyncWebSocket: public AsyncWebHandler {
|
||||||
|
private:
|
||||||
|
String _url;
|
||||||
|
std::list<AsyncWebSocketClient> _clients;
|
||||||
|
uint32_t _cNextId;
|
||||||
|
AwsEventHandler _eventHandler;
|
||||||
|
AwsHandshakeHandler _handshakeHandler;
|
||||||
|
bool _enabled;
|
||||||
|
AsyncWebLock _lock;
|
||||||
|
|
||||||
|
public:
|
||||||
|
AsyncWebSocket(const String& url);
|
||||||
|
~AsyncWebSocket();
|
||||||
|
const char * url() const { return _url.c_str(); }
|
||||||
|
void enable(bool e){ _enabled = e; }
|
||||||
|
bool enabled() const { return _enabled; }
|
||||||
|
bool availableForWriteAll();
|
||||||
|
bool availableForWrite(uint32_t id);
|
||||||
|
|
||||||
|
size_t count() const;
|
||||||
|
AsyncWebSocketClient * client(uint32_t id);
|
||||||
|
bool hasClient(uint32_t id){ return client(id) != NULL; }
|
||||||
|
|
||||||
|
void close(uint32_t id, uint16_t code=0, const char * message=NULL);
|
||||||
|
void closeAll(uint16_t code=0, const char * message=NULL);
|
||||||
|
void cleanupClients(uint16_t maxClients = DEFAULT_MAX_WS_CLIENTS);
|
||||||
|
|
||||||
|
void ping(uint32_t id, const uint8_t *data=NULL, size_t len=0);
|
||||||
|
void pingAll(const uint8_t *data=NULL, size_t len=0); // done
|
||||||
|
|
||||||
|
void text(uint32_t id, const uint8_t * message, size_t len);
|
||||||
|
void text(uint32_t id, const char *message, size_t len);
|
||||||
|
void text(uint32_t id, const char *message);
|
||||||
|
void text(uint32_t id, const String &message);
|
||||||
|
void text(uint32_t id, const __FlashStringHelper *message);
|
||||||
|
void text(uint32_t id, AsyncWebSocketMessageBuffer *buffer);
|
||||||
|
void text(uint32_t id, std::shared_ptr<std::vector<uint8_t>> buffer);
|
||||||
|
|
||||||
|
void textAll(const uint8_t *message, size_t len);
|
||||||
|
void textAll(const char * message, size_t len);
|
||||||
|
void textAll(const char * message);
|
||||||
|
void textAll(const String &message);
|
||||||
|
void textAll(const __FlashStringHelper *message);
|
||||||
|
void textAll(AsyncWebSocketMessageBuffer *buffer);
|
||||||
|
void textAll(std::shared_ptr<std::vector<uint8_t>> buffer);
|
||||||
|
|
||||||
|
void binary(uint32_t id, const uint8_t *message, size_t len);
|
||||||
|
void binary(uint32_t id, const char *message, size_t len);
|
||||||
|
void binary(uint32_t id, const char *message);
|
||||||
|
void binary(uint32_t id, const String &message);
|
||||||
|
void binary(uint32_t id, const __FlashStringHelper *message, size_t len);
|
||||||
|
void binary(uint32_t id, AsyncWebSocketMessageBuffer *buffer);
|
||||||
|
void binary(uint32_t id, std::shared_ptr<std::vector<uint8_t>> buffer);
|
||||||
|
|
||||||
|
void binaryAll(const uint8_t *message, size_t len);
|
||||||
|
void binaryAll(const char *message, size_t len);
|
||||||
|
void binaryAll(const char *message);
|
||||||
|
void binaryAll(const String &message);
|
||||||
|
void binaryAll(const __FlashStringHelper *message, size_t len);
|
||||||
|
void binaryAll(AsyncWebSocketMessageBuffer *buffer);
|
||||||
|
void binaryAll(std::shared_ptr<std::vector<uint8_t>> buffer);
|
||||||
|
|
||||||
|
size_t printf(uint32_t id, const char *format, ...) __attribute__ ((format (printf, 3, 4)));
|
||||||
|
size_t printfAll(const char *format, ...) __attribute__ ((format (printf, 2, 3)));
|
||||||
|
#ifndef ESP32
|
||||||
|
size_t printf_P(uint32_t id, PGM_P formatP, ...) __attribute__ ((format (printf, 3, 4)));
|
||||||
|
#endif
|
||||||
|
size_t printfAll_P(PGM_P formatP, ...) __attribute__ ((format (printf, 2, 3)));
|
||||||
|
|
||||||
|
//event listener
|
||||||
|
void onEvent(AwsEventHandler handler){
|
||||||
|
_eventHandler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handshake Handler
|
||||||
|
void handleHandshake(AwsHandshakeHandler handler){
|
||||||
|
_handshakeHandler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
//system callbacks (do not call)
|
||||||
|
uint32_t _getNextId(){ return _cNextId++; }
|
||||||
|
AsyncWebSocketClient *_newClient(AsyncWebServerRequest *request);
|
||||||
|
void _handleEvent(AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len);
|
||||||
|
virtual bool canHandle(AsyncWebServerRequest *request) override final;
|
||||||
|
virtual void handleRequest(AsyncWebServerRequest *request) override final;
|
||||||
|
|
||||||
|
|
||||||
|
// messagebuffer functions/objects.
|
||||||
|
AsyncWebSocketMessageBuffer * makeBuffer(size_t size = 0);
|
||||||
|
AsyncWebSocketMessageBuffer * makeBuffer(uint8_t * data, size_t size);
|
||||||
|
|
||||||
|
const std::list<AsyncWebSocketClient> &getClients() const { return _clients; }
|
||||||
|
};
|
||||||
|
|
||||||
|
//WebServer response to authenticate the socket and detach the tcp client from the web server request
|
||||||
|
class AsyncWebSocketResponse: public AsyncWebServerResponse {
|
||||||
|
private:
|
||||||
|
String _content;
|
||||||
|
AsyncWebSocket *_server;
|
||||||
|
public:
|
||||||
|
AsyncWebSocketResponse(const String& key, AsyncWebSocket *server);
|
||||||
|
void _respond(AsyncWebServerRequest *request);
|
||||||
|
size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time);
|
||||||
|
bool _sourceValid() const { return true; }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* ASYNCWEBSOCKET_H_ */
|
||||||
134
libraries/ESPAsyncWebServer/src/AsyncWebSynchronization.h
Normal file
134
libraries/ESPAsyncWebServer/src/AsyncWebSynchronization.h
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
#ifndef ASYNCWEBSYNCHRONIZATION_H_
|
||||||
|
#define ASYNCWEBSYNCHRONIZATION_H_
|
||||||
|
|
||||||
|
// Synchronisation is only available on ESP32, as the ESP8266 isn't using FreeRTOS by default
|
||||||
|
|
||||||
|
#include <ESPAsyncWebServer.h>
|
||||||
|
|
||||||
|
#ifdef ESP32
|
||||||
|
|
||||||
|
// This is the ESP32 version of the Sync Lock, using the FreeRTOS Semaphore
|
||||||
|
// Modified 'AsyncWebLock' to just only use mutex since pxCurrentTCB is not
|
||||||
|
// always available. According to example by Arjan Filius, changed name,
|
||||||
|
// added unimplemented version for ESP8266
|
||||||
|
class AsyncPlainLock
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
SemaphoreHandle_t _lock;
|
||||||
|
|
||||||
|
public:
|
||||||
|
AsyncPlainLock() {
|
||||||
|
_lock = xSemaphoreCreateBinary();
|
||||||
|
// In this fails, the system is likely that much out of memory that
|
||||||
|
// we should abort anyways. If assertions are disabled, nothing is lost..
|
||||||
|
assert(_lock);
|
||||||
|
xSemaphoreGive(_lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
~AsyncPlainLock() {
|
||||||
|
vSemaphoreDelete(_lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool lock() const {
|
||||||
|
xSemaphoreTake(_lock, portMAX_DELAY);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void unlock() const {
|
||||||
|
xSemaphoreGive(_lock);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// This is the ESP32 version of the Sync Lock, using the FreeRTOS Semaphore
|
||||||
|
class AsyncWebLock
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
SemaphoreHandle_t _lock;
|
||||||
|
mutable TaskHandle_t _lockedBy{};
|
||||||
|
|
||||||
|
public:
|
||||||
|
AsyncWebLock()
|
||||||
|
{
|
||||||
|
_lock = xSemaphoreCreateBinary();
|
||||||
|
// In this fails, the system is likely that much out of memory that
|
||||||
|
// we should abort anyways. If assertions are disabled, nothing is lost..
|
||||||
|
assert(_lock);
|
||||||
|
_lockedBy = NULL;
|
||||||
|
xSemaphoreGive(_lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
~AsyncWebLock() {
|
||||||
|
vSemaphoreDelete(_lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool lock() const {
|
||||||
|
const auto currentTask = xTaskGetCurrentTaskHandle();
|
||||||
|
if (_lockedBy != currentTask) {
|
||||||
|
xSemaphoreTake(_lock, portMAX_DELAY);
|
||||||
|
_lockedBy = currentTask;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void unlock() const {
|
||||||
|
_lockedBy = NULL;
|
||||||
|
xSemaphoreGive(_lock);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
// This is the 8266 version of the Sync Lock which is currently unimplemented
|
||||||
|
class AsyncWebLock
|
||||||
|
{
|
||||||
|
|
||||||
|
public:
|
||||||
|
AsyncWebLock() {
|
||||||
|
}
|
||||||
|
|
||||||
|
~AsyncWebLock() {
|
||||||
|
}
|
||||||
|
|
||||||
|
bool lock() const {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void unlock() const {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Same for AsyncPlainLock, for ESP8266 this is just the unimplemented version above.
|
||||||
|
using AsyncPlainLock = AsyncWebLock;
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class AsyncWebLockGuard
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
const AsyncWebLock *_lock;
|
||||||
|
|
||||||
|
public:
|
||||||
|
AsyncWebLockGuard(const AsyncWebLock &l) {
|
||||||
|
if (l.lock()) {
|
||||||
|
_lock = &l;
|
||||||
|
} else {
|
||||||
|
_lock = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~AsyncWebLockGuard() {
|
||||||
|
if (_lock) {
|
||||||
|
_lock->unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void unlock() {
|
||||||
|
if (_lock) {
|
||||||
|
_lock->unlock();
|
||||||
|
_lock = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // ASYNCWEBSYNCHRONIZATION_H_
|
||||||
501
libraries/ESPAsyncWebServer/src/ESPAsyncWebServer.h
Normal file
501
libraries/ESPAsyncWebServer/src/ESPAsyncWebServer.h
Normal file
@@ -0,0 +1,501 @@
|
|||||||
|
/*
|
||||||
|
Asynchronous WebServer library for Espressif MCUs
|
||||||
|
|
||||||
|
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
|
||||||
|
This file is part of the esp8266 core for Arduino environment.
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
#ifndef _ESPAsyncWebServer_H_
|
||||||
|
#define _ESPAsyncWebServer_H_
|
||||||
|
|
||||||
|
#include "Arduino.h"
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <list>
|
||||||
|
#include <vector>
|
||||||
|
#include "FS.h"
|
||||||
|
|
||||||
|
#include "StringArray.h"
|
||||||
|
|
||||||
|
#ifdef ESP32
|
||||||
|
#include <WiFi.h>
|
||||||
|
#include <AsyncTCP.h>
|
||||||
|
#elif defined(ESP8266)
|
||||||
|
#include <ESP8266WiFi.h>
|
||||||
|
#include <ESPAsyncTCP.h>
|
||||||
|
#else
|
||||||
|
#error Platform not supported
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define ASYNCWEBSERVER_VERSION "2.10.4"
|
||||||
|
#define ASYNCWEBSERVER_VERSION_MAJOR 2
|
||||||
|
#define ASYNCWEBSERVER_VERSION_MINOR 10
|
||||||
|
#define ASYNCWEBSERVER_VERSION_REVISION 4
|
||||||
|
#define ASYNCWEBSERVER_FORK_mathieucarbou
|
||||||
|
|
||||||
|
#ifdef ASYNCWEBSERVER_REGEX
|
||||||
|
#define ASYNCWEBSERVER_REGEX_ATTRIBUTE
|
||||||
|
#else
|
||||||
|
#define ASYNCWEBSERVER_REGEX_ATTRIBUTE __attribute__((warning("ASYNCWEBSERVER_REGEX not defined")))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class AsyncWebServer;
|
||||||
|
class AsyncWebServerRequest;
|
||||||
|
class AsyncWebServerResponse;
|
||||||
|
class AsyncWebHeader;
|
||||||
|
class AsyncWebParameter;
|
||||||
|
class AsyncWebRewrite;
|
||||||
|
class AsyncWebHandler;
|
||||||
|
class AsyncStaticWebHandler;
|
||||||
|
class AsyncCallbackWebHandler;
|
||||||
|
class AsyncResponseStream;
|
||||||
|
|
||||||
|
#ifndef WEBSERVER_H
|
||||||
|
typedef enum {
|
||||||
|
HTTP_GET = 0b00000001,
|
||||||
|
HTTP_POST = 0b00000010,
|
||||||
|
HTTP_DELETE = 0b00000100,
|
||||||
|
HTTP_PUT = 0b00001000,
|
||||||
|
HTTP_PATCH = 0b00010000,
|
||||||
|
HTTP_HEAD = 0b00100000,
|
||||||
|
HTTP_OPTIONS = 0b01000000,
|
||||||
|
HTTP_ANY = 0b01111111,
|
||||||
|
} WebRequestMethod;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef HAVE_FS_FILE_OPEN_MODE
|
||||||
|
namespace fs {
|
||||||
|
class FileOpenMode {
|
||||||
|
public:
|
||||||
|
static const char *read;
|
||||||
|
static const char *write;
|
||||||
|
static const char *append;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
#else
|
||||||
|
#include "FileOpenMode.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//if this value is returned when asked for data, packet will not be sent and you will be asked for data again
|
||||||
|
#define RESPONSE_TRY_AGAIN 0xFFFFFFFF
|
||||||
|
|
||||||
|
typedef uint8_t WebRequestMethodComposite;
|
||||||
|
typedef std::function<void(void)> ArDisconnectHandler;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PARAMETER :: Chainable object to hold GET/POST and FILE parameters
|
||||||
|
* */
|
||||||
|
|
||||||
|
class AsyncWebParameter {
|
||||||
|
private:
|
||||||
|
String _name;
|
||||||
|
String _value;
|
||||||
|
size_t _size;
|
||||||
|
bool _isForm;
|
||||||
|
bool _isFile;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
AsyncWebParameter(const String& name, const String& value, bool form=false, bool file=false, size_t size=0): _name(name), _value(value), _size(size), _isForm(form), _isFile(file){}
|
||||||
|
const String& name() const { return _name; }
|
||||||
|
const String& value() const { return _value; }
|
||||||
|
size_t size() const { return _size; }
|
||||||
|
bool isPost() const { return _isForm; }
|
||||||
|
bool isFile() const { return _isFile; }
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* HEADER :: Chainable object to hold the headers
|
||||||
|
* */
|
||||||
|
|
||||||
|
class AsyncWebHeader {
|
||||||
|
private:
|
||||||
|
String _name;
|
||||||
|
String _value;
|
||||||
|
|
||||||
|
public:
|
||||||
|
AsyncWebHeader() = default;
|
||||||
|
AsyncWebHeader(const AsyncWebHeader &) = default;
|
||||||
|
|
||||||
|
AsyncWebHeader(const String& name, const String& value): _name(name), _value(value){}
|
||||||
|
AsyncWebHeader(const String& data): _name(), _value(){
|
||||||
|
if(!data) return;
|
||||||
|
int index = data.indexOf(':');
|
||||||
|
if (index < 0) return;
|
||||||
|
_name = data.substring(0, index);
|
||||||
|
_value = data.substring(index + 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncWebHeader &operator=(const AsyncWebHeader &) = default;
|
||||||
|
|
||||||
|
const String& name() const { return _name; }
|
||||||
|
const String& value() const { return _value; }
|
||||||
|
String toString() const { return _name + F(": ") + _value + F("\r\n"); }
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* REQUEST :: Each incoming Client is wrapped inside a Request and both live together until disconnect
|
||||||
|
* */
|
||||||
|
|
||||||
|
typedef enum { RCT_NOT_USED = -1, RCT_DEFAULT = 0, RCT_HTTP, RCT_WS, RCT_EVENT, RCT_MAX } RequestedConnectionType;
|
||||||
|
|
||||||
|
typedef std::function<size_t(uint8_t*, size_t, size_t)> AwsResponseFiller;
|
||||||
|
typedef std::function<String(const String&)> AwsTemplateProcessor;
|
||||||
|
|
||||||
|
class AsyncWebServerRequest {
|
||||||
|
using File = fs::File;
|
||||||
|
using FS = fs::FS;
|
||||||
|
friend class AsyncWebServer;
|
||||||
|
friend class AsyncCallbackWebHandler;
|
||||||
|
private:
|
||||||
|
AsyncClient* _client;
|
||||||
|
AsyncWebServer* _server;
|
||||||
|
AsyncWebHandler* _handler;
|
||||||
|
AsyncWebServerResponse* _response;
|
||||||
|
std::vector<String> _interestingHeaders;
|
||||||
|
ArDisconnectHandler _onDisconnectfn;
|
||||||
|
|
||||||
|
String _temp;
|
||||||
|
uint8_t _parseState;
|
||||||
|
|
||||||
|
uint8_t _version;
|
||||||
|
WebRequestMethodComposite _method;
|
||||||
|
String _url;
|
||||||
|
String _host;
|
||||||
|
String _contentType;
|
||||||
|
String _boundary;
|
||||||
|
String _authorization;
|
||||||
|
RequestedConnectionType _reqconntype;
|
||||||
|
void _removeNotInterestingHeaders();
|
||||||
|
bool _isDigest;
|
||||||
|
bool _isMultipart;
|
||||||
|
bool _isPlainPost;
|
||||||
|
bool _expectingContinue;
|
||||||
|
size_t _contentLength;
|
||||||
|
size_t _parsedLength;
|
||||||
|
|
||||||
|
std::list<AsyncWebHeader> _headers;
|
||||||
|
AlternativeLinkedList<AsyncWebParameter *> _params;
|
||||||
|
std::vector<String> _pathParams;
|
||||||
|
|
||||||
|
uint8_t _multiParseState;
|
||||||
|
uint8_t _boundaryPosition;
|
||||||
|
size_t _itemStartIndex;
|
||||||
|
size_t _itemSize;
|
||||||
|
String _itemName;
|
||||||
|
String _itemFilename;
|
||||||
|
String _itemType;
|
||||||
|
String _itemValue;
|
||||||
|
uint8_t *_itemBuffer;
|
||||||
|
size_t _itemBufferIndex;
|
||||||
|
bool _itemIsFile;
|
||||||
|
|
||||||
|
void _onPoll();
|
||||||
|
void _onAck(size_t len, uint32_t time);
|
||||||
|
void _onError(int8_t error);
|
||||||
|
void _onTimeout(uint32_t time);
|
||||||
|
void _onDisconnect();
|
||||||
|
void _onData(void *buf, size_t len);
|
||||||
|
|
||||||
|
void _addParam(AsyncWebParameter*);
|
||||||
|
void _addPathParam(const char *param);
|
||||||
|
|
||||||
|
bool _parseReqHead();
|
||||||
|
bool _parseReqHeader();
|
||||||
|
void _parseLine();
|
||||||
|
void _parsePlainPostChar(uint8_t data);
|
||||||
|
void _parseMultipartPostByte(uint8_t data, bool last);
|
||||||
|
void _addGetParams(const String& params);
|
||||||
|
|
||||||
|
void _handleUploadStart();
|
||||||
|
void _handleUploadByte(uint8_t data, bool last);
|
||||||
|
void _handleUploadEnd();
|
||||||
|
|
||||||
|
public:
|
||||||
|
File _tempFile;
|
||||||
|
void *_tempObject;
|
||||||
|
|
||||||
|
AsyncWebServerRequest(AsyncWebServer*, AsyncClient*);
|
||||||
|
~AsyncWebServerRequest();
|
||||||
|
|
||||||
|
AsyncClient* client(){ return _client; }
|
||||||
|
uint8_t version() const { return _version; }
|
||||||
|
WebRequestMethodComposite method() const { return _method; }
|
||||||
|
const String& url() const { return _url; }
|
||||||
|
const String& host() const { return _host; }
|
||||||
|
const String& contentType() const { return _contentType; }
|
||||||
|
size_t contentLength() const { return _contentLength; }
|
||||||
|
bool multipart() const { return _isMultipart; }
|
||||||
|
const __FlashStringHelper *methodToString() const;
|
||||||
|
const __FlashStringHelper *requestedConnTypeToString() const;
|
||||||
|
RequestedConnectionType requestedConnType() const { return _reqconntype; }
|
||||||
|
bool isExpectedRequestedConnType(RequestedConnectionType erct1, RequestedConnectionType erct2 = RCT_NOT_USED, RequestedConnectionType erct3 = RCT_NOT_USED);
|
||||||
|
void onDisconnect (ArDisconnectHandler fn);
|
||||||
|
|
||||||
|
//hash is the string representation of:
|
||||||
|
// base64(user:pass) for basic or
|
||||||
|
// user:realm:md5(user:realm:pass) for digest
|
||||||
|
bool authenticate(const char * hash);
|
||||||
|
bool authenticate(const char * username, const char * password, const char * realm = NULL, bool passwordIsHash = false);
|
||||||
|
void requestAuthentication(const char * realm = NULL, bool isDigest = true);
|
||||||
|
|
||||||
|
void setHandler(AsyncWebHandler *handler){ _handler = handler; }
|
||||||
|
void addInterestingHeader(const String& name);
|
||||||
|
|
||||||
|
void redirect(const String& url);
|
||||||
|
|
||||||
|
void send(AsyncWebServerResponse *response);
|
||||||
|
void send(int code, const String& contentType=String(), const String& content=String());
|
||||||
|
void send(FS &fs, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr);
|
||||||
|
void send(File content, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr);
|
||||||
|
void send(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback=nullptr);
|
||||||
|
void send(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr);
|
||||||
|
void sendChunked(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr);
|
||||||
|
void send_P(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback=nullptr);
|
||||||
|
void send_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback=nullptr);
|
||||||
|
|
||||||
|
AsyncWebServerResponse *beginResponse(int code, const String& contentType=String(), const String& content=String());
|
||||||
|
AsyncWebServerResponse *beginResponse(FS &fs, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr);
|
||||||
|
AsyncWebServerResponse *beginResponse(File content, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr);
|
||||||
|
AsyncWebServerResponse *beginResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback=nullptr);
|
||||||
|
AsyncWebServerResponse *beginResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr);
|
||||||
|
AsyncWebServerResponse *beginChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr);
|
||||||
|
AsyncResponseStream *beginResponseStream(const String& contentType, size_t bufferSize=1460);
|
||||||
|
AsyncWebServerResponse *beginResponse_P(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback=nullptr);
|
||||||
|
AsyncWebServerResponse *beginResponse_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback=nullptr);
|
||||||
|
|
||||||
|
size_t headers() const; // get header count
|
||||||
|
bool hasHeader(const String& name) const; // check if header exists
|
||||||
|
bool hasHeader(const __FlashStringHelper * data) const; // check if header exists
|
||||||
|
|
||||||
|
AsyncWebHeader* getHeader(const String& name);
|
||||||
|
const AsyncWebHeader* getHeader(const String& name) const;
|
||||||
|
AsyncWebHeader* getHeader(const __FlashStringHelper * data);
|
||||||
|
const AsyncWebHeader* getHeader(const __FlashStringHelper * data) const;
|
||||||
|
AsyncWebHeader* getHeader(size_t num);
|
||||||
|
const AsyncWebHeader* getHeader(size_t num) const;
|
||||||
|
|
||||||
|
size_t params() const; // get arguments count
|
||||||
|
bool hasParam(const String& name, bool post=false, bool file=false) const;
|
||||||
|
bool hasParam(const __FlashStringHelper * data, bool post=false, bool file=false) const;
|
||||||
|
|
||||||
|
AsyncWebParameter* getParam(const String& name, bool post=false, bool file=false) const;
|
||||||
|
AsyncWebParameter* getParam(const __FlashStringHelper * data, bool post, bool file) const;
|
||||||
|
AsyncWebParameter* getParam(size_t num) const;
|
||||||
|
|
||||||
|
size_t args() const { return params(); } // get arguments count
|
||||||
|
const String& arg(const String& name) const; // get request argument value by name
|
||||||
|
const String& arg(const __FlashStringHelper * data) const; // get request argument value by F(name)
|
||||||
|
const String& arg(size_t i) const; // get request argument value by number
|
||||||
|
const String& argName(size_t i) const; // get request argument name by number
|
||||||
|
bool hasArg(const char* name) const; // check if argument exists
|
||||||
|
bool hasArg(const __FlashStringHelper * data) const; // check if F(argument) exists
|
||||||
|
|
||||||
|
const String& ASYNCWEBSERVER_REGEX_ATTRIBUTE pathArg(size_t i) const;
|
||||||
|
|
||||||
|
const String& header(const char* name) const;// get request header value by name
|
||||||
|
const String& header(const __FlashStringHelper * data) const;// get request header value by F(name)
|
||||||
|
const String& header(size_t i) const; // get request header value by number
|
||||||
|
const String& headerName(size_t i) const; // get request header name by number
|
||||||
|
String urlDecode(const String& text) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* FILTER :: Callback to filter AsyncWebRewrite and AsyncWebHandler (done by the Server)
|
||||||
|
* */
|
||||||
|
|
||||||
|
typedef std::function<bool(AsyncWebServerRequest *request)> ArRequestFilterFunction;
|
||||||
|
|
||||||
|
bool ON_STA_FILTER(AsyncWebServerRequest *request);
|
||||||
|
|
||||||
|
bool ON_AP_FILTER(AsyncWebServerRequest *request);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* REWRITE :: One instance can be handle any Request (done by the Server)
|
||||||
|
* */
|
||||||
|
|
||||||
|
class AsyncWebRewrite {
|
||||||
|
protected:
|
||||||
|
String _from;
|
||||||
|
String _toUrl;
|
||||||
|
String _params;
|
||||||
|
ArRequestFilterFunction _filter;
|
||||||
|
public:
|
||||||
|
AsyncWebRewrite(const char* from, const char* to): _from(from), _toUrl(to), _params(String()), _filter(NULL){
|
||||||
|
int index = _toUrl.indexOf('?');
|
||||||
|
if (index > 0) {
|
||||||
|
_params = _toUrl.substring(index +1);
|
||||||
|
_toUrl = _toUrl.substring(0, index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
virtual ~AsyncWebRewrite(){}
|
||||||
|
AsyncWebRewrite& setFilter(ArRequestFilterFunction fn) { _filter = fn; return *this; }
|
||||||
|
bool filter(AsyncWebServerRequest *request) const { return _filter == NULL || _filter(request); }
|
||||||
|
const String& from(void) const { return _from; }
|
||||||
|
const String& toUrl(void) const { return _toUrl; }
|
||||||
|
const String& params(void) const { return _params; }
|
||||||
|
virtual bool match(AsyncWebServerRequest *request) { return from() == request->url() && filter(request); }
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* HANDLER :: One instance can be attached to any Request (done by the Server)
|
||||||
|
* */
|
||||||
|
|
||||||
|
class AsyncWebHandler {
|
||||||
|
protected:
|
||||||
|
ArRequestFilterFunction _filter;
|
||||||
|
String _username;
|
||||||
|
String _password;
|
||||||
|
public:
|
||||||
|
AsyncWebHandler():_username(""), _password(""){}
|
||||||
|
AsyncWebHandler& setFilter(ArRequestFilterFunction fn) { _filter = fn; return *this; }
|
||||||
|
AsyncWebHandler& setAuthentication(const char *username, const char *password){ _username = String(username);_password = String(password); return *this; };
|
||||||
|
AsyncWebHandler& setAuthentication(const String& username, const String& password){ _username = username;_password = password; return *this; };
|
||||||
|
bool filter(AsyncWebServerRequest *request){ return _filter == NULL || _filter(request); }
|
||||||
|
virtual ~AsyncWebHandler(){}
|
||||||
|
virtual bool canHandle(AsyncWebServerRequest *request __attribute__((unused))){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
virtual void handleRequest(AsyncWebServerRequest *request __attribute__((unused))){}
|
||||||
|
virtual void handleUpload(AsyncWebServerRequest *request __attribute__((unused)), const String& filename __attribute__((unused)), size_t index __attribute__((unused)), uint8_t *data __attribute__((unused)), size_t len __attribute__((unused)), bool final __attribute__((unused))){}
|
||||||
|
virtual void handleBody(AsyncWebServerRequest *request __attribute__((unused)), uint8_t *data __attribute__((unused)), size_t len __attribute__((unused)), size_t index __attribute__((unused)), size_t total __attribute__((unused))){}
|
||||||
|
virtual bool isRequestHandlerTrivial(){return true;}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* RESPONSE :: One instance is created for each Request (attached by the Handler)
|
||||||
|
* */
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
RESPONSE_SETUP, RESPONSE_HEADERS, RESPONSE_CONTENT, RESPONSE_WAIT_ACK, RESPONSE_END, RESPONSE_FAILED
|
||||||
|
} WebResponseState;
|
||||||
|
|
||||||
|
class AsyncWebServerResponse {
|
||||||
|
protected:
|
||||||
|
int _code;
|
||||||
|
std::list<AsyncWebHeader> _headers;
|
||||||
|
String _contentType;
|
||||||
|
size_t _contentLength;
|
||||||
|
bool _sendContentLength;
|
||||||
|
bool _chunked;
|
||||||
|
size_t _headLength;
|
||||||
|
size_t _sentLength;
|
||||||
|
size_t _ackedLength;
|
||||||
|
size_t _writtenLength;
|
||||||
|
WebResponseState _state;
|
||||||
|
const char* _responseCodeToString(int code);
|
||||||
|
public:
|
||||||
|
static const __FlashStringHelper *responseCodeToString(int code);
|
||||||
|
|
||||||
|
public:
|
||||||
|
AsyncWebServerResponse();
|
||||||
|
virtual ~AsyncWebServerResponse();
|
||||||
|
virtual void setCode(int code);
|
||||||
|
virtual void setContentLength(size_t len);
|
||||||
|
virtual void setContentType(const String& type);
|
||||||
|
virtual void addHeader(const String& name, const String& value);
|
||||||
|
virtual String _assembleHead(uint8_t version);
|
||||||
|
virtual bool _started() const;
|
||||||
|
virtual bool _finished() const;
|
||||||
|
virtual bool _failed() const;
|
||||||
|
virtual bool _sourceValid() const;
|
||||||
|
virtual void _respond(AsyncWebServerRequest *request);
|
||||||
|
virtual size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time);
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* SERVER :: One instance
|
||||||
|
* */
|
||||||
|
|
||||||
|
typedef std::function<void(AsyncWebServerRequest *request)> ArRequestHandlerFunction;
|
||||||
|
typedef std::function<void(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final)> ArUploadHandlerFunction;
|
||||||
|
typedef std::function<void(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total)> ArBodyHandlerFunction;
|
||||||
|
|
||||||
|
class AsyncWebServer {
|
||||||
|
protected:
|
||||||
|
AsyncServer _server;
|
||||||
|
AlternativeLinkedList<AsyncWebRewrite*> _rewrites;
|
||||||
|
AlternativeLinkedList<AsyncWebHandler*> _handlers;
|
||||||
|
AsyncCallbackWebHandler* _catchAllHandler;
|
||||||
|
|
||||||
|
public:
|
||||||
|
AsyncWebServer(uint16_t port);
|
||||||
|
~AsyncWebServer();
|
||||||
|
|
||||||
|
void begin();
|
||||||
|
void end();
|
||||||
|
|
||||||
|
#if ASYNC_TCP_SSL_ENABLED
|
||||||
|
void onSslFileRequest(AcSSlFileHandler cb, void* arg);
|
||||||
|
void beginSecure(const char *cert, const char *private_key_file, const char *password);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
AsyncWebRewrite& addRewrite(AsyncWebRewrite* rewrite);
|
||||||
|
bool removeRewrite(AsyncWebRewrite* rewrite);
|
||||||
|
AsyncWebRewrite& rewrite(const char* from, const char* to);
|
||||||
|
|
||||||
|
AsyncWebHandler& addHandler(AsyncWebHandler* handler);
|
||||||
|
bool removeHandler(AsyncWebHandler* handler);
|
||||||
|
|
||||||
|
AsyncCallbackWebHandler& on(const char* uri, ArRequestHandlerFunction onRequest);
|
||||||
|
AsyncCallbackWebHandler& on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest);
|
||||||
|
AsyncCallbackWebHandler& on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload);
|
||||||
|
AsyncCallbackWebHandler& on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody);
|
||||||
|
|
||||||
|
AsyncStaticWebHandler& serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_control = NULL);
|
||||||
|
|
||||||
|
void onNotFound(ArRequestHandlerFunction fn); //called when handler is not assigned
|
||||||
|
void onFileUpload(ArUploadHandlerFunction fn); //handle file uploads
|
||||||
|
void onRequestBody(ArBodyHandlerFunction fn); //handle posts with plain body content (JSON often transmitted this way as a request)
|
||||||
|
|
||||||
|
void reset(); //remove all writers and handlers, with onNotFound/onFileUpload/onRequestBody
|
||||||
|
|
||||||
|
void _handleDisconnect(AsyncWebServerRequest *request);
|
||||||
|
void _attachHandler(AsyncWebServerRequest *request);
|
||||||
|
void _rewriteRequest(AsyncWebServerRequest *request);
|
||||||
|
};
|
||||||
|
|
||||||
|
class DefaultHeaders {
|
||||||
|
using headers_t = std::list<AsyncWebHeader>;
|
||||||
|
headers_t _headers;
|
||||||
|
|
||||||
|
public:
|
||||||
|
DefaultHeaders() = default;
|
||||||
|
|
||||||
|
using ConstIterator = headers_t::const_iterator;
|
||||||
|
|
||||||
|
void addHeader(const String& name, const String& value){
|
||||||
|
_headers.emplace_back(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
ConstIterator begin() const { return _headers.begin(); }
|
||||||
|
ConstIterator end() const { return _headers.end(); }
|
||||||
|
|
||||||
|
DefaultHeaders(DefaultHeaders const &) = delete;
|
||||||
|
DefaultHeaders &operator=(DefaultHeaders const &) = delete;
|
||||||
|
|
||||||
|
static DefaultHeaders &Instance() {
|
||||||
|
static DefaultHeaders instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#include "WebResponseImpl.h"
|
||||||
|
#include "WebHandlerImpl.h"
|
||||||
|
#include "AsyncWebSocket.h"
|
||||||
|
#include "AsyncEventSource.h"
|
||||||
|
|
||||||
|
#endif /* _AsyncWebServer_H_ */
|
||||||
2
libraries/ESPAsyncWebServer/src/ESP_Async_WebServer.h
Normal file
2
libraries/ESPAsyncWebServer/src/ESP_Async_WebServer.h
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
// to please Arduino Lint
|
||||||
|
#include "ESPAsyncWebServer.h"
|
||||||
174
libraries/ESPAsyncWebServer/src/StringArray.h
Normal file
174
libraries/ESPAsyncWebServer/src/StringArray.h
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
/*
|
||||||
|
Asynchronous WebServer library for Espressif MCUs
|
||||||
|
|
||||||
|
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
|
||||||
|
This file is part of the esp8266 core for Arduino environment.
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
#ifndef STRINGARRAY_H_
|
||||||
|
#define STRINGARRAY_H_
|
||||||
|
|
||||||
|
#include "stddef.h"
|
||||||
|
#include "WString.h"
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class AlternativeLinkedListNode {
|
||||||
|
T _value;
|
||||||
|
public:
|
||||||
|
AlternativeLinkedListNode<T>* next;
|
||||||
|
AlternativeLinkedListNode(const T val): _value(val), next(nullptr) {}
|
||||||
|
~AlternativeLinkedListNode(){}
|
||||||
|
const T& value() const { return _value; };
|
||||||
|
T& value(){ return _value; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T, template<typename> class Item = AlternativeLinkedListNode>
|
||||||
|
class AlternativeLinkedList {
|
||||||
|
public:
|
||||||
|
typedef Item<T> ItemType;
|
||||||
|
typedef std::function<void(const T&)> OnRemove;
|
||||||
|
typedef std::function<bool(const T&)> Predicate;
|
||||||
|
private:
|
||||||
|
ItemType* _root;
|
||||||
|
OnRemove _onRemove;
|
||||||
|
|
||||||
|
class Iterator {
|
||||||
|
ItemType* _node;
|
||||||
|
public:
|
||||||
|
Iterator(ItemType* current = nullptr) : _node(current) {}
|
||||||
|
Iterator(const Iterator& i) : _node(i._node) {}
|
||||||
|
Iterator& operator ++() { _node = _node->next; return *this; }
|
||||||
|
bool operator != (const Iterator& i) const { return _node != i._node; }
|
||||||
|
const T& operator * () const { return _node->value(); }
|
||||||
|
const T* operator -> () const { return &_node->value(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
typedef const Iterator ConstIterator;
|
||||||
|
ConstIterator begin() const { return ConstIterator(_root); }
|
||||||
|
ConstIterator end() const { return ConstIterator(nullptr); }
|
||||||
|
|
||||||
|
AlternativeLinkedList(OnRemove onRemove) : _root(nullptr), _onRemove(onRemove) {}
|
||||||
|
~AlternativeLinkedList(){}
|
||||||
|
void add(const T& t){
|
||||||
|
auto it = new ItemType(t);
|
||||||
|
if(!_root){
|
||||||
|
_root = it;
|
||||||
|
} else {
|
||||||
|
auto i = _root;
|
||||||
|
while(i->next) i = i->next;
|
||||||
|
i->next = it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
T& front() const {
|
||||||
|
return _root->value();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isEmpty() const {
|
||||||
|
return _root == nullptr;
|
||||||
|
}
|
||||||
|
size_t length() const {
|
||||||
|
size_t i = 0;
|
||||||
|
auto it = _root;
|
||||||
|
while(it){
|
||||||
|
i++;
|
||||||
|
it = it->next;
|
||||||
|
}
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
size_t count_if(Predicate predicate) const {
|
||||||
|
size_t i = 0;
|
||||||
|
auto it = _root;
|
||||||
|
while(it){
|
||||||
|
if (!predicate){
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
else if (predicate(it->value())) {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
it = it->next;
|
||||||
|
}
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
const T* nth(size_t N) const {
|
||||||
|
size_t i = 0;
|
||||||
|
auto it = _root;
|
||||||
|
while(it){
|
||||||
|
if(i++ == N)
|
||||||
|
return &(it->value());
|
||||||
|
it = it->next;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
bool remove(const T& t){
|
||||||
|
auto it = _root;
|
||||||
|
auto pit = _root;
|
||||||
|
while(it){
|
||||||
|
if(it->value() == t){
|
||||||
|
if(it == _root){
|
||||||
|
_root = _root->next;
|
||||||
|
} else {
|
||||||
|
pit->next = it->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_onRemove) {
|
||||||
|
_onRemove(it->value());
|
||||||
|
}
|
||||||
|
|
||||||
|
delete it;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
pit = it;
|
||||||
|
it = it->next;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bool remove_first(Predicate predicate){
|
||||||
|
auto it = _root;
|
||||||
|
auto pit = _root;
|
||||||
|
while(it){
|
||||||
|
if(predicate(it->value())){
|
||||||
|
if(it == _root){
|
||||||
|
_root = _root->next;
|
||||||
|
} else {
|
||||||
|
pit->next = it->next;
|
||||||
|
}
|
||||||
|
if (_onRemove) {
|
||||||
|
_onRemove(it->value());
|
||||||
|
}
|
||||||
|
delete it;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
pit = it;
|
||||||
|
it = it->next;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void free(){
|
||||||
|
while(_root != nullptr){
|
||||||
|
auto it = _root;
|
||||||
|
_root = _root->next;
|
||||||
|
if (_onRemove) {
|
||||||
|
_onRemove(it->value());
|
||||||
|
}
|
||||||
|
delete it;
|
||||||
|
}
|
||||||
|
_root = nullptr;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* STRINGARRAY_H_ */
|
||||||
247
libraries/ESPAsyncWebServer/src/WebAuthentication.cpp
Normal file
247
libraries/ESPAsyncWebServer/src/WebAuthentication.cpp
Normal file
@@ -0,0 +1,247 @@
|
|||||||
|
/*
|
||||||
|
Asynchronous WebServer library for Espressif MCUs
|
||||||
|
|
||||||
|
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
|
||||||
|
This file is part of the esp8266 core for Arduino environment.
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
#include "WebAuthentication.h"
|
||||||
|
#include <libb64/cencode.h>
|
||||||
|
#ifdef ESP32
|
||||||
|
#include "mbedtls/md5.h"
|
||||||
|
#else
|
||||||
|
#include "md5.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
// Basic Auth hash = base64("username:password")
|
||||||
|
|
||||||
|
bool checkBasicAuthentication(const char * hash, const char * username, const char * password){
|
||||||
|
if(username == NULL || password == NULL || hash == NULL)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
size_t toencodeLen = strlen(username)+strlen(password)+1;
|
||||||
|
size_t encodedLen = base64_encode_expected_len(toencodeLen);
|
||||||
|
if(strlen(hash) != encodedLen)
|
||||||
|
// Fix from https://github.com/me-no-dev/ESPAsyncWebServer/issues/667
|
||||||
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
|
if(strlen(hash) != encodedLen)
|
||||||
|
#else
|
||||||
|
if (strlen(hash) != encodedLen - 1)
|
||||||
|
#endif
|
||||||
|
return false;
|
||||||
|
|
||||||
|
char *toencode = new char[toencodeLen+1];
|
||||||
|
if(toencode == NULL){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
char *encoded = new char[base64_encode_expected_len(toencodeLen)+1];
|
||||||
|
if(encoded == NULL){
|
||||||
|
delete[] toencode;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
sprintf_P(toencode, PSTR("%s:%s"), username, password);
|
||||||
|
if(base64_encode_chars(toencode, toencodeLen, encoded) > 0 && memcmp(hash, encoded, encodedLen) == 0){
|
||||||
|
delete[] toencode;
|
||||||
|
delete[] encoded;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
delete[] toencode;
|
||||||
|
delete[] encoded;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool getMD5(uint8_t * data, uint16_t len, char * output){//33 bytes or more
|
||||||
|
#ifdef ESP32
|
||||||
|
mbedtls_md5_context _ctx;
|
||||||
|
#else
|
||||||
|
md5_context_t _ctx;
|
||||||
|
#endif
|
||||||
|
uint8_t i;
|
||||||
|
uint8_t * _buf = (uint8_t*)malloc(16);
|
||||||
|
if(_buf == NULL)
|
||||||
|
return false;
|
||||||
|
memset(_buf, 0x00, 16);
|
||||||
|
#ifdef ESP32
|
||||||
|
mbedtls_md5_init(&_ctx);
|
||||||
|
#if ESP_IDF_VERSION_MAJOR == 5
|
||||||
|
mbedtls_md5_starts(&_ctx);
|
||||||
|
mbedtls_md5_update(&_ctx, data, len);
|
||||||
|
mbedtls_md5_finish(&_ctx, _buf);
|
||||||
|
#else
|
||||||
|
mbedtls_md5_starts_ret(&_ctx);
|
||||||
|
mbedtls_md5_update_ret(&_ctx, data, len);
|
||||||
|
mbedtls_md5_finish_ret(&_ctx, _buf);
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
MD5Init(&_ctx);
|
||||||
|
MD5Update(&_ctx, data, len);
|
||||||
|
MD5Final(_buf, &_ctx);
|
||||||
|
#endif
|
||||||
|
for(i = 0; i < 16; i++) {
|
||||||
|
sprintf_P(output + (i * 2), PSTR("%02x"), _buf[i]);
|
||||||
|
}
|
||||||
|
free(_buf);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String genRandomMD5(){
|
||||||
|
#ifdef ESP8266
|
||||||
|
uint32_t r = RANDOM_REG32;
|
||||||
|
#else
|
||||||
|
uint32_t r = rand();
|
||||||
|
#endif
|
||||||
|
char * out = (char*)malloc(33);
|
||||||
|
if(out == NULL || !getMD5((uint8_t*)(&r), 4, out))
|
||||||
|
return emptyString;
|
||||||
|
String res = String(out);
|
||||||
|
free(out);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String stringMD5(const String& in){
|
||||||
|
char * out = (char*)malloc(33);
|
||||||
|
if(out == NULL || !getMD5((uint8_t*)(in.c_str()), in.length(), out))
|
||||||
|
return emptyString;
|
||||||
|
String res = String(out);
|
||||||
|
free(out);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
String generateDigestHash(const char * username, const char * password, const char * realm){
|
||||||
|
if(username == NULL || password == NULL || realm == NULL){
|
||||||
|
return emptyString;
|
||||||
|
}
|
||||||
|
char * out = (char*)malloc(33);
|
||||||
|
String res = String(username);
|
||||||
|
res += ':';
|
||||||
|
res.concat(realm);
|
||||||
|
res += ':';
|
||||||
|
String in = res;
|
||||||
|
in.concat(password);
|
||||||
|
if(out == NULL || !getMD5((uint8_t*)(in.c_str()), in.length(), out))
|
||||||
|
return emptyString;
|
||||||
|
res.concat(out);
|
||||||
|
free(out);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
String requestDigestAuthentication(const char * realm){
|
||||||
|
String header = F("realm=\"");
|
||||||
|
if(realm == NULL)
|
||||||
|
header.concat(F("asyncesp"));
|
||||||
|
else
|
||||||
|
header.concat(realm);
|
||||||
|
header.concat(F("\", qop=\"auth\", nonce=\""));
|
||||||
|
header.concat(genRandomMD5());
|
||||||
|
header.concat(F("\", opaque=\""));
|
||||||
|
header.concat(genRandomMD5());
|
||||||
|
header += '"';
|
||||||
|
return header;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool checkDigestAuthentication(const char * header, const __FlashStringHelper *method, const char * username, const char * password, const char * realm, bool passwordIsHash, const char * nonce, const char * opaque, const char * uri){
|
||||||
|
if(username == NULL || password == NULL || header == NULL || method == NULL){
|
||||||
|
//os_printf("AUTH FAIL: missing requred fields\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String myHeader = String(header);
|
||||||
|
int nextBreak = myHeader.indexOf(',');
|
||||||
|
if(nextBreak < 0){
|
||||||
|
//os_printf("AUTH FAIL: no variables\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String myUsername = String();
|
||||||
|
String myRealm = String();
|
||||||
|
String myNonce = String();
|
||||||
|
String myUri = String();
|
||||||
|
String myResponse = String();
|
||||||
|
String myQop = String();
|
||||||
|
String myNc = String();
|
||||||
|
String myCnonce = String();
|
||||||
|
|
||||||
|
myHeader += F(", ");
|
||||||
|
do {
|
||||||
|
String avLine = myHeader.substring(0, nextBreak);
|
||||||
|
avLine.trim();
|
||||||
|
myHeader = myHeader.substring(nextBreak+1);
|
||||||
|
nextBreak = myHeader.indexOf(',');
|
||||||
|
|
||||||
|
int eqSign = avLine.indexOf('=');
|
||||||
|
if(eqSign < 0){
|
||||||
|
//os_printf("AUTH FAIL: no = sign\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String varName = avLine.substring(0, eqSign);
|
||||||
|
avLine = avLine.substring(eqSign + 1);
|
||||||
|
if(avLine.startsWith(String('"'))){
|
||||||
|
avLine = avLine.substring(1, avLine.length() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(varName.equals(F("username"))){
|
||||||
|
if(!avLine.equals(username)){
|
||||||
|
//os_printf("AUTH FAIL: username\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
myUsername = avLine;
|
||||||
|
} else if(varName.equals(F("realm"))){
|
||||||
|
if(realm != NULL && !avLine.equals(realm)){
|
||||||
|
//os_printf("AUTH FAIL: realm\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
myRealm = avLine;
|
||||||
|
} else if(varName.equals(F("nonce"))){
|
||||||
|
if(nonce != NULL && !avLine.equals(nonce)){
|
||||||
|
//os_printf("AUTH FAIL: nonce\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
myNonce = avLine;
|
||||||
|
} else if(varName.equals(F("opaque"))){
|
||||||
|
if(opaque != NULL && !avLine.equals(opaque)){
|
||||||
|
//os_printf("AUTH FAIL: opaque\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if(varName.equals(F("uri"))){
|
||||||
|
if(uri != NULL && !avLine.equals(uri)){
|
||||||
|
//os_printf("AUTH FAIL: uri\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
myUri = avLine;
|
||||||
|
} else if(varName.equals(F("response"))){
|
||||||
|
myResponse = avLine;
|
||||||
|
} else if(varName.equals(F("qop"))){
|
||||||
|
myQop = avLine;
|
||||||
|
} else if(varName.equals(F("nc"))){
|
||||||
|
myNc = avLine;
|
||||||
|
} else if(varName.equals(F("cnonce"))){
|
||||||
|
myCnonce = avLine;
|
||||||
|
}
|
||||||
|
} while(nextBreak > 0);
|
||||||
|
|
||||||
|
String ha1 = (passwordIsHash) ? String(password) : stringMD5(myUsername + ':' + myRealm + ':' + String(password));
|
||||||
|
String ha2 = String(method) + ':' + myUri;
|
||||||
|
String response = ha1 + ':' + myNonce + ':' + myNc + ':' + myCnonce + ':' + myQop + ':' + stringMD5(ha2);
|
||||||
|
|
||||||
|
if(myResponse.equals(stringMD5(response))){
|
||||||
|
//os_printf("AUTH SUCCESS\n");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//os_printf("AUTH FAIL: password\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
34
libraries/ESPAsyncWebServer/src/WebAuthentication.h
Normal file
34
libraries/ESPAsyncWebServer/src/WebAuthentication.h
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
Asynchronous WebServer library for Espressif MCUs
|
||||||
|
|
||||||
|
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
|
||||||
|
This file is part of the esp8266 core for Arduino environment.
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef WEB_AUTHENTICATION_H_
|
||||||
|
#define WEB_AUTHENTICATION_H_
|
||||||
|
|
||||||
|
#include "Arduino.h"
|
||||||
|
|
||||||
|
bool checkBasicAuthentication(const char * header, const char * username, const char * password);
|
||||||
|
String requestDigestAuthentication(const char * realm);
|
||||||
|
bool checkDigestAuthentication(const char * header, const __FlashStringHelper *method, const char * username, const char * password, const char * realm, bool passwordIsHash, const char * nonce, const char * opaque, const char * uri);
|
||||||
|
|
||||||
|
//for storing hashed versions on the device that can be authenticated against
|
||||||
|
String generateDigestHash(const char * username, const char * password, const char * realm);
|
||||||
|
|
||||||
|
#endif
|
||||||
151
libraries/ESPAsyncWebServer/src/WebHandlerImpl.h
Normal file
151
libraries/ESPAsyncWebServer/src/WebHandlerImpl.h
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
/*
|
||||||
|
Asynchronous WebServer library for Espressif MCUs
|
||||||
|
|
||||||
|
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
|
||||||
|
This file is part of the esp8266 core for Arduino environment.
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
#ifndef ASYNCWEBSERVERHANDLERIMPL_H_
|
||||||
|
#define ASYNCWEBSERVERHANDLERIMPL_H_
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#ifdef ASYNCWEBSERVER_REGEX
|
||||||
|
#include <regex>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "stddef.h"
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
class AsyncStaticWebHandler: public AsyncWebHandler {
|
||||||
|
using File = fs::File;
|
||||||
|
using FS = fs::FS;
|
||||||
|
private:
|
||||||
|
bool _getFile(AsyncWebServerRequest *request);
|
||||||
|
bool _fileExists(AsyncWebServerRequest *request, const String& path);
|
||||||
|
uint8_t _countBits(const uint8_t value) const;
|
||||||
|
protected:
|
||||||
|
FS _fs;
|
||||||
|
String _uri;
|
||||||
|
String _path;
|
||||||
|
String _default_file;
|
||||||
|
String _cache_control;
|
||||||
|
String _last_modified;
|
||||||
|
AwsTemplateProcessor _callback;
|
||||||
|
bool _isDir;
|
||||||
|
bool _gzipFirst;
|
||||||
|
uint8_t _gzipStats;
|
||||||
|
public:
|
||||||
|
AsyncStaticWebHandler(const char* uri, FS& fs, const char* path, const char* cache_control);
|
||||||
|
virtual bool canHandle(AsyncWebServerRequest *request) override final;
|
||||||
|
virtual void handleRequest(AsyncWebServerRequest *request) override final;
|
||||||
|
AsyncStaticWebHandler& setIsDir(bool isDir);
|
||||||
|
AsyncStaticWebHandler& setDefaultFile(const char* filename);
|
||||||
|
AsyncStaticWebHandler& setCacheControl(const char* cache_control);
|
||||||
|
AsyncStaticWebHandler& setLastModified(const char* last_modified);
|
||||||
|
AsyncStaticWebHandler& setLastModified(struct tm* last_modified);
|
||||||
|
#ifdef ESP8266
|
||||||
|
AsyncStaticWebHandler& setLastModified(time_t last_modified);
|
||||||
|
AsyncStaticWebHandler& setLastModified(); //sets to current time. Make sure sntp is runing and time is updated
|
||||||
|
#endif
|
||||||
|
AsyncStaticWebHandler& setTemplateProcessor(AwsTemplateProcessor newCallback) {_callback = newCallback; return *this;}
|
||||||
|
};
|
||||||
|
|
||||||
|
class AsyncCallbackWebHandler: public AsyncWebHandler {
|
||||||
|
private:
|
||||||
|
protected:
|
||||||
|
String _uri;
|
||||||
|
WebRequestMethodComposite _method;
|
||||||
|
ArRequestHandlerFunction _onRequest;
|
||||||
|
ArUploadHandlerFunction _onUpload;
|
||||||
|
ArBodyHandlerFunction _onBody;
|
||||||
|
bool _isRegex;
|
||||||
|
public:
|
||||||
|
AsyncCallbackWebHandler() : _uri(), _method(HTTP_ANY), _onRequest(NULL), _onUpload(NULL), _onBody(NULL), _isRegex(false) {}
|
||||||
|
void setUri(const String& uri){
|
||||||
|
_uri = uri;
|
||||||
|
_isRegex = uri.startsWith("^") && uri.endsWith("$");
|
||||||
|
}
|
||||||
|
void setMethod(WebRequestMethodComposite method){ _method = method; }
|
||||||
|
void onRequest(ArRequestHandlerFunction fn){ _onRequest = fn; }
|
||||||
|
void onUpload(ArUploadHandlerFunction fn){ _onUpload = fn; }
|
||||||
|
void onBody(ArBodyHandlerFunction fn){ _onBody = fn; }
|
||||||
|
|
||||||
|
virtual bool canHandle(AsyncWebServerRequest *request) override final{
|
||||||
|
|
||||||
|
if(!_onRequest)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if(!(_method & request->method()))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
#ifdef ASYNCWEBSERVER_REGEX
|
||||||
|
if (_isRegex) {
|
||||||
|
std::regex pattern(_uri.c_str());
|
||||||
|
std::smatch matches;
|
||||||
|
std::string s(request->url().c_str());
|
||||||
|
if(std::regex_search(s, matches, pattern)) {
|
||||||
|
for (size_t i = 1; i < matches.size(); ++i) { // start from 1
|
||||||
|
request->_addPathParam(matches[i].str().c_str());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
#endif
|
||||||
|
if (_uri.length() && _uri.startsWith("/*.")) {
|
||||||
|
String uriTemplate = String (_uri);
|
||||||
|
uriTemplate = uriTemplate.substring(uriTemplate.lastIndexOf("."));
|
||||||
|
if (!request->url().endsWith(uriTemplate))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
if (_uri.length() && _uri.endsWith("*")) {
|
||||||
|
String uriTemplate = String(_uri);
|
||||||
|
uriTemplate = uriTemplate.substring(0, uriTemplate.length() - 1);
|
||||||
|
if (!request->url().startsWith(uriTemplate))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if(_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri+"/")))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
request->addInterestingHeader("ANY");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void handleRequest(AsyncWebServerRequest *request) override final {
|
||||||
|
if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str()))
|
||||||
|
return request->requestAuthentication();
|
||||||
|
if(_onRequest)
|
||||||
|
_onRequest(request);
|
||||||
|
else
|
||||||
|
request->send(500);
|
||||||
|
}
|
||||||
|
virtual void handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) override final {
|
||||||
|
if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str()))
|
||||||
|
return request->requestAuthentication();
|
||||||
|
if(_onUpload)
|
||||||
|
_onUpload(request, filename, index, data, len, final);
|
||||||
|
}
|
||||||
|
virtual void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override final {
|
||||||
|
if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str()))
|
||||||
|
return request->requestAuthentication();
|
||||||
|
if(_onBody)
|
||||||
|
_onBody(request, data, len, index, total);
|
||||||
|
}
|
||||||
|
virtual bool isRequestHandlerTrivial() override final {return _onRequest ? false : true;}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* ASYNCWEBSERVERHANDLERIMPL_H_ */
|
||||||
233
libraries/ESPAsyncWebServer/src/WebHandlers.cpp
Normal file
233
libraries/ESPAsyncWebServer/src/WebHandlers.cpp
Normal file
@@ -0,0 +1,233 @@
|
|||||||
|
/*
|
||||||
|
Asynchronous WebServer library for Espressif MCUs
|
||||||
|
|
||||||
|
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
|
||||||
|
This file is part of the esp8266 core for Arduino environment.
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
#include "ESPAsyncWebServer.h"
|
||||||
|
#include "WebHandlerImpl.h"
|
||||||
|
|
||||||
|
AsyncStaticWebHandler::AsyncStaticWebHandler(const char* uri, FS& fs, const char* path, const char* cache_control)
|
||||||
|
: _fs(fs), _uri(uri), _path(path), _default_file(F("index.htm")), _cache_control(cache_control), _last_modified(), _callback(nullptr)
|
||||||
|
{
|
||||||
|
// Ensure leading '/'
|
||||||
|
if (_uri.length() == 0 || _uri[0] != '/') _uri = String('/') + _uri;
|
||||||
|
if (_path.length() == 0 || _path[0] != '/') _path = String('/') + _path;
|
||||||
|
|
||||||
|
// If path ends with '/' we assume a hint that this is a directory to improve performance.
|
||||||
|
// However - if it does not end with '/' we, can't assume a file, path can still be a directory.
|
||||||
|
_isDir = _path[_path.length()-1] == '/';
|
||||||
|
|
||||||
|
// Remove the trailing '/' so we can handle default file
|
||||||
|
// Notice that root will be "" not "/"
|
||||||
|
if (_uri[_uri.length()-1] == '/') _uri = _uri.substring(0, _uri.length()-1);
|
||||||
|
if (_path[_path.length()-1] == '/') _path = _path.substring(0, _path.length()-1);
|
||||||
|
|
||||||
|
// Reset stats
|
||||||
|
_gzipFirst = false;
|
||||||
|
_gzipStats = 0xF8;
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncStaticWebHandler& AsyncStaticWebHandler::setIsDir(bool isDir){
|
||||||
|
_isDir = isDir;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncStaticWebHandler& AsyncStaticWebHandler::setDefaultFile(const char* filename){
|
||||||
|
_default_file = String(filename);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncStaticWebHandler& AsyncStaticWebHandler::setCacheControl(const char* cache_control){
|
||||||
|
_cache_control = String(cache_control);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(const char* last_modified){
|
||||||
|
_last_modified = last_modified;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(struct tm* last_modified){
|
||||||
|
auto formatP = PSTR("%a, %d %b %Y %H:%M:%S %Z");
|
||||||
|
char format[strlen_P(formatP) + 1];
|
||||||
|
strcpy_P(format, formatP);
|
||||||
|
|
||||||
|
char result[30];
|
||||||
|
strftime(result, sizeof(result), format, last_modified);
|
||||||
|
return setLastModified((const char *)result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef ESP8266
|
||||||
|
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(time_t last_modified){
|
||||||
|
return setLastModified((struct tm *)gmtime(&last_modified));
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(){
|
||||||
|
time_t last_modified;
|
||||||
|
if(time(&last_modified) == 0) //time is not yet set
|
||||||
|
return *this;
|
||||||
|
return setLastModified(last_modified);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
bool AsyncStaticWebHandler::canHandle(AsyncWebServerRequest *request){
|
||||||
|
if(request->method() != HTTP_GET
|
||||||
|
|| !request->url().startsWith(_uri)
|
||||||
|
|| !request->isExpectedRequestedConnType(RCT_DEFAULT, RCT_HTTP)
|
||||||
|
){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (_getFile(request)) {
|
||||||
|
// We interested in "If-Modified-Since" header to check if file was modified
|
||||||
|
if (_last_modified.length())
|
||||||
|
request->addInterestingHeader(F("If-Modified-Since"));
|
||||||
|
|
||||||
|
if(_cache_control.length())
|
||||||
|
request->addInterestingHeader(F("If-None-Match"));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AsyncStaticWebHandler::_getFile(AsyncWebServerRequest *request)
|
||||||
|
{
|
||||||
|
// Remove the found uri
|
||||||
|
String path = request->url().substring(_uri.length());
|
||||||
|
|
||||||
|
// We can skip the file check and look for default if request is to the root of a directory or that request path ends with '/'
|
||||||
|
bool canSkipFileCheck = (_isDir && path.length() == 0) || (path.length() && path[path.length()-1] == '/');
|
||||||
|
|
||||||
|
path = _path + path;
|
||||||
|
|
||||||
|
// Do we have a file or .gz file
|
||||||
|
if (!canSkipFileCheck && _fileExists(request, path))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// Can't handle if not default file
|
||||||
|
if (_default_file.length() == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Try to add default file, ensure there is a trailing '/' ot the path.
|
||||||
|
if (path.length() == 0 || path[path.length()-1] != '/')
|
||||||
|
path += String('/');
|
||||||
|
path += _default_file;
|
||||||
|
|
||||||
|
return _fileExists(request, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef ESP32
|
||||||
|
#define FILE_IS_REAL(f) (f == true && !f.isDirectory())
|
||||||
|
#else
|
||||||
|
#define FILE_IS_REAL(f) (f == true)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool AsyncStaticWebHandler::_fileExists(AsyncWebServerRequest *request, const String& path)
|
||||||
|
{
|
||||||
|
bool fileFound = false;
|
||||||
|
bool gzipFound = false;
|
||||||
|
|
||||||
|
String gzip = path + F(".gz");
|
||||||
|
|
||||||
|
if (_gzipFirst) {
|
||||||
|
if (_fs.exists(gzip)) {
|
||||||
|
request->_tempFile = _fs.open(gzip, fs::FileOpenMode::read);
|
||||||
|
gzipFound = FILE_IS_REAL(request->_tempFile);
|
||||||
|
}
|
||||||
|
if (!gzipFound){
|
||||||
|
if (_fs.exists(path)) {
|
||||||
|
request->_tempFile = _fs.open(path, fs::FileOpenMode::read);
|
||||||
|
fileFound = FILE_IS_REAL(request->_tempFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (_fs.exists(path)) {
|
||||||
|
request->_tempFile = _fs.open(path, fs::FileOpenMode::read);
|
||||||
|
fileFound = FILE_IS_REAL(request->_tempFile);
|
||||||
|
}
|
||||||
|
if (!fileFound){
|
||||||
|
if (_fs.exists(gzip)) {
|
||||||
|
request->_tempFile = _fs.open(gzip, fs::FileOpenMode::read);
|
||||||
|
gzipFound = FILE_IS_REAL(request->_tempFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool found = fileFound || gzipFound;
|
||||||
|
|
||||||
|
if (found) {
|
||||||
|
// Extract the file name from the path and keep it in _tempObject
|
||||||
|
size_t pathLen = path.length();
|
||||||
|
char * _tempPath = (char*)malloc(pathLen+1);
|
||||||
|
snprintf_P(_tempPath, pathLen+1, PSTR("%s"), path.c_str());
|
||||||
|
request->_tempObject = (void*)_tempPath;
|
||||||
|
|
||||||
|
// Calculate gzip statistic
|
||||||
|
_gzipStats = (_gzipStats << 1) + (gzipFound ? 1 : 0);
|
||||||
|
if (_gzipStats == 0x00) _gzipFirst = false; // All files are not gzip
|
||||||
|
else if (_gzipStats == 0xFF) _gzipFirst = true; // All files are gzip
|
||||||
|
else _gzipFirst = _countBits(_gzipStats) > 4; // IF we have more gzip files - try gzip first
|
||||||
|
}
|
||||||
|
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t AsyncStaticWebHandler::_countBits(const uint8_t value) const
|
||||||
|
{
|
||||||
|
uint8_t w = value;
|
||||||
|
uint8_t n;
|
||||||
|
for (n=0; w!=0; n++) w&=w-1;
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest *request)
|
||||||
|
{
|
||||||
|
// Get the filename from request->_tempObject and free it
|
||||||
|
String filename = String((char*)request->_tempObject);
|
||||||
|
free(request->_tempObject);
|
||||||
|
request->_tempObject = NULL;
|
||||||
|
if((_username.length() && _password.length()) && !request->authenticate(_username.c_str(), _password.c_str()))
|
||||||
|
return request->requestAuthentication();
|
||||||
|
|
||||||
|
if (request->_tempFile == true) {
|
||||||
|
time_t lw = request->_tempFile.getLastWrite(); // get last file mod time (if supported by FS)
|
||||||
|
if (lw) setLastModified(gmtime(&lw));
|
||||||
|
String etag(lw ? lw : request->_tempFile.size()); // set etag to lastmod timestamp if available, otherwise to size
|
||||||
|
if (_last_modified.length() && _last_modified == request->header(F("If-Modified-Since"))) {
|
||||||
|
request->_tempFile.close();
|
||||||
|
request->send(304); // Not modified
|
||||||
|
} else if (_cache_control.length() && request->hasHeader(F("If-None-Match")) && request->header(F("If-None-Match")).equals(etag)) {
|
||||||
|
request->_tempFile.close();
|
||||||
|
AsyncWebServerResponse * response = new AsyncBasicResponse(304); // Not modified
|
||||||
|
response->addHeader(F("Cache-Control"), _cache_control);
|
||||||
|
response->addHeader(F("ETag"), etag);
|
||||||
|
request->send(response);
|
||||||
|
} else {
|
||||||
|
AsyncWebServerResponse * response = new AsyncFileResponse(request->_tempFile, filename, String(), false, _callback);
|
||||||
|
if (_last_modified.length())
|
||||||
|
response->addHeader(F("Last-Modified"), _last_modified);
|
||||||
|
if (_cache_control.length()){
|
||||||
|
response->addHeader(F("Cache-Control"), _cache_control);
|
||||||
|
response->addHeader(F("ETag"), etag);
|
||||||
|
}
|
||||||
|
request->send(response);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
request->send(404);
|
||||||
|
}
|
||||||
|
}
|
||||||
993
libraries/ESPAsyncWebServer/src/WebRequest.cpp
Normal file
993
libraries/ESPAsyncWebServer/src/WebRequest.cpp
Normal file
@@ -0,0 +1,993 @@
|
|||||||
|
/*
|
||||||
|
Asynchronous WebServer library for Espressif MCUs
|
||||||
|
|
||||||
|
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
|
||||||
|
This file is part of the esp8266 core for Arduino environment.
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
#include "ESPAsyncWebServer.h"
|
||||||
|
#include "WebResponseImpl.h"
|
||||||
|
#include "WebAuthentication.h"
|
||||||
|
|
||||||
|
#ifndef ESP8266
|
||||||
|
#define os_strlen strlen
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define __is_param_char(c) ((c) && ((c) != '{') && ((c) != '[') && ((c) != '&') && ((c) != '='))
|
||||||
|
|
||||||
|
enum { PARSE_REQ_START, PARSE_REQ_HEADERS, PARSE_REQ_BODY, PARSE_REQ_END, PARSE_REQ_FAIL };
|
||||||
|
|
||||||
|
AsyncWebServerRequest::AsyncWebServerRequest(AsyncWebServer* s, AsyncClient* c)
|
||||||
|
: _client(c)
|
||||||
|
, _server(s)
|
||||||
|
, _handler(NULL)
|
||||||
|
, _response(NULL)
|
||||||
|
, _temp()
|
||||||
|
, _parseState(0)
|
||||||
|
, _version(0)
|
||||||
|
, _method(HTTP_ANY)
|
||||||
|
, _url()
|
||||||
|
, _host()
|
||||||
|
, _contentType()
|
||||||
|
, _boundary()
|
||||||
|
, _authorization()
|
||||||
|
, _reqconntype(RCT_HTTP)
|
||||||
|
, _isDigest(false)
|
||||||
|
, _isMultipart(false)
|
||||||
|
, _isPlainPost(false)
|
||||||
|
, _expectingContinue(false)
|
||||||
|
, _contentLength(0)
|
||||||
|
, _parsedLength(0)
|
||||||
|
, _params(AlternativeLinkedList<AsyncWebParameter *>([](AsyncWebParameter *p){ delete p; }))
|
||||||
|
, _multiParseState(0)
|
||||||
|
, _boundaryPosition(0)
|
||||||
|
, _itemStartIndex(0)
|
||||||
|
, _itemSize(0)
|
||||||
|
, _itemName()
|
||||||
|
, _itemFilename()
|
||||||
|
, _itemType()
|
||||||
|
, _itemValue()
|
||||||
|
, _itemBuffer(0)
|
||||||
|
, _itemBufferIndex(0)
|
||||||
|
, _itemIsFile(false)
|
||||||
|
, _tempObject(NULL)
|
||||||
|
{
|
||||||
|
c->onError([](void *r, AsyncClient* c, int8_t error){ (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onError(error); }, this);
|
||||||
|
c->onAck([](void *r, AsyncClient* c, size_t len, uint32_t time){ (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onAck(len, time); }, this);
|
||||||
|
c->onDisconnect([](void *r, AsyncClient* c){ AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onDisconnect(); delete c; }, this);
|
||||||
|
c->onTimeout([](void *r, AsyncClient* c, uint32_t time){ (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onTimeout(time); }, this);
|
||||||
|
c->onData([](void *r, AsyncClient* c, void *buf, size_t len){ (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onData(buf, len); }, this);
|
||||||
|
c->onPoll([](void *r, AsyncClient* c){ (void)c; AsyncWebServerRequest *req = ( AsyncWebServerRequest*)r; req->_onPoll(); }, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncWebServerRequest::~AsyncWebServerRequest(){
|
||||||
|
_headers.clear();
|
||||||
|
|
||||||
|
_params.free();
|
||||||
|
_pathParams.clear();
|
||||||
|
|
||||||
|
_interestingHeaders.clear();
|
||||||
|
|
||||||
|
if(_response != NULL){
|
||||||
|
delete _response;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(_tempObject != NULL){
|
||||||
|
free(_tempObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(_tempFile){
|
||||||
|
_tempFile.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(_itemBuffer){
|
||||||
|
free(_itemBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncWebServerRequest::_onData(void *buf, size_t len){
|
||||||
|
size_t i = 0;
|
||||||
|
while (true) {
|
||||||
|
|
||||||
|
if(_parseState < PARSE_REQ_BODY){
|
||||||
|
// Find new line in buf
|
||||||
|
char *str = (char*)buf;
|
||||||
|
for (i = 0; i < len; i++) {
|
||||||
|
if (str[i] == '\n') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (i == len) { // No new line, just add the buffer in _temp
|
||||||
|
char ch = str[len-1];
|
||||||
|
str[len-1] = 0;
|
||||||
|
_temp.reserve(_temp.length()+len);
|
||||||
|
_temp.concat(str);
|
||||||
|
_temp.concat(ch);
|
||||||
|
} else { // Found new line - extract it and parse
|
||||||
|
str[i] = 0; // Terminate the string at the end of the line.
|
||||||
|
_temp.concat(str);
|
||||||
|
_temp.trim();
|
||||||
|
_parseLine();
|
||||||
|
if (++i < len) {
|
||||||
|
// Still have more buffer to process
|
||||||
|
buf = str+i;
|
||||||
|
len-= i;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if(_parseState == PARSE_REQ_BODY){
|
||||||
|
// A handler should be already attached at this point in _parseLine function.
|
||||||
|
// If handler does nothing (_onRequest is NULL), we don't need to really parse the body.
|
||||||
|
const bool needParse = _handler && !_handler->isRequestHandlerTrivial();
|
||||||
|
if(_isMultipart){
|
||||||
|
if(needParse){
|
||||||
|
size_t i;
|
||||||
|
for(i=0; i<len; i++){
|
||||||
|
_parseMultipartPostByte(((uint8_t*)buf)[i], i == len - 1);
|
||||||
|
_parsedLength++;
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
_parsedLength += len;
|
||||||
|
} else {
|
||||||
|
if(_parsedLength == 0){
|
||||||
|
if(_contentType.startsWith(F("application/x-www-form-urlencoded"))){
|
||||||
|
_isPlainPost = true;
|
||||||
|
} else if(_contentType == F("text/plain") && __is_param_char(((char*)buf)[0])){
|
||||||
|
size_t i = 0;
|
||||||
|
while (i<len && __is_param_char(((char*)buf)[i++]));
|
||||||
|
if(i < len && ((char*)buf)[i-1] == '='){
|
||||||
|
_isPlainPost = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!_isPlainPost) {
|
||||||
|
//check if authenticated before calling the body
|
||||||
|
if(_handler) _handler->handleBody(this, (uint8_t*)buf, len, _parsedLength, _contentLength);
|
||||||
|
_parsedLength += len;
|
||||||
|
} else if(needParse) {
|
||||||
|
size_t i;
|
||||||
|
for(i=0; i<len; i++){
|
||||||
|
_parsedLength++;
|
||||||
|
_parsePlainPostChar(((uint8_t*)buf)[i]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_parsedLength += len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(_parsedLength == _contentLength){
|
||||||
|
_parseState = PARSE_REQ_END;
|
||||||
|
//check if authenticated before calling handleRequest and request auth instead
|
||||||
|
if(_handler) _handler->handleRequest(this);
|
||||||
|
else send(501);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncWebServerRequest::_removeNotInterestingHeaders(){
|
||||||
|
if (std::any_of(std::begin(_interestingHeaders), std::end(_interestingHeaders),
|
||||||
|
[](const String &str){ return str.equalsIgnoreCase(F("ANY")); }))
|
||||||
|
return; // nothing to do
|
||||||
|
|
||||||
|
for(auto iter = std::begin(_headers); iter != std::end(_headers); )
|
||||||
|
{
|
||||||
|
const auto name = iter->name();
|
||||||
|
|
||||||
|
if (std::none_of(std::begin(_interestingHeaders), std::end(_interestingHeaders),
|
||||||
|
[&name](const String &str){ return str.equalsIgnoreCase(name); }))
|
||||||
|
iter = _headers.erase(iter);
|
||||||
|
else
|
||||||
|
iter++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncWebServerRequest::_onPoll(){
|
||||||
|
//os_printf("p\n");
|
||||||
|
if(_response != NULL && _client != NULL && _client->canSend()){
|
||||||
|
if(!_response->_finished()){
|
||||||
|
_response->_ack(this, 0, 0);
|
||||||
|
} else {
|
||||||
|
AsyncWebServerResponse* r = _response;
|
||||||
|
_response = NULL;
|
||||||
|
delete r;
|
||||||
|
|
||||||
|
_client->close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncWebServerRequest::_onAck(size_t len, uint32_t time){
|
||||||
|
//os_printf("a:%u:%u\n", len, time);
|
||||||
|
if(_response != NULL){
|
||||||
|
if(!_response->_finished()){
|
||||||
|
_response->_ack(this, len, time);
|
||||||
|
} else if(_response->_finished()){
|
||||||
|
AsyncWebServerResponse* r = _response;
|
||||||
|
_response = NULL;
|
||||||
|
delete r;
|
||||||
|
|
||||||
|
_client->close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncWebServerRequest::_onError(int8_t error){
|
||||||
|
(void)error;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncWebServerRequest::_onTimeout(uint32_t time){
|
||||||
|
(void)time;
|
||||||
|
//os_printf("TIMEOUT: %u, state: %s\n", time, _client->stateToString());
|
||||||
|
_client->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncWebServerRequest::onDisconnect (ArDisconnectHandler fn){
|
||||||
|
_onDisconnectfn=fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncWebServerRequest::_onDisconnect(){
|
||||||
|
//os_printf("d\n");
|
||||||
|
if(_onDisconnectfn) {
|
||||||
|
_onDisconnectfn();
|
||||||
|
}
|
||||||
|
_server->_handleDisconnect(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncWebServerRequest::_addParam(AsyncWebParameter *p){
|
||||||
|
_params.add(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncWebServerRequest::_addPathParam(const char *p){
|
||||||
|
_pathParams.emplace_back(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncWebServerRequest::_addGetParams(const String& params){
|
||||||
|
size_t start = 0;
|
||||||
|
while (start < params.length()){
|
||||||
|
int end = params.indexOf('&', start);
|
||||||
|
if (end < 0) end = params.length();
|
||||||
|
int equal = params.indexOf('=', start);
|
||||||
|
if (equal < 0 || equal > end) equal = end;
|
||||||
|
String name = params.substring(start, equal);
|
||||||
|
String value = equal + 1 < end ? params.substring(equal + 1, end) : String();
|
||||||
|
_addParam(new AsyncWebParameter(urlDecode(name), urlDecode(value)));
|
||||||
|
start = end + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AsyncWebServerRequest::_parseReqHead(){
|
||||||
|
// Split the head into method, url and version
|
||||||
|
int index = _temp.indexOf(' ');
|
||||||
|
String m = _temp.substring(0, index);
|
||||||
|
index = _temp.indexOf(' ', index+1);
|
||||||
|
String u = _temp.substring(m.length()+1, index);
|
||||||
|
_temp = _temp.substring(index+1);
|
||||||
|
|
||||||
|
if(m == F("GET")){
|
||||||
|
_method = HTTP_GET;
|
||||||
|
} else if(m == F("POST")){
|
||||||
|
_method = HTTP_POST;
|
||||||
|
} else if(m == F("DELETE")){
|
||||||
|
_method = HTTP_DELETE;
|
||||||
|
} else if(m == F("PUT")){
|
||||||
|
_method = HTTP_PUT;
|
||||||
|
} else if(m == F("PATCH")){
|
||||||
|
_method = HTTP_PATCH;
|
||||||
|
} else if(m == F("HEAD")){
|
||||||
|
_method = HTTP_HEAD;
|
||||||
|
} else if(m == F("OPTIONS")){
|
||||||
|
_method = HTTP_OPTIONS;
|
||||||
|
}
|
||||||
|
|
||||||
|
String g;
|
||||||
|
index = u.indexOf('?');
|
||||||
|
if(index > 0){
|
||||||
|
g = u.substring(index +1);
|
||||||
|
u = u.substring(0, index);
|
||||||
|
}
|
||||||
|
_url = urlDecode(u);
|
||||||
|
_addGetParams(g);
|
||||||
|
|
||||||
|
if(!_temp.startsWith(F("HTTP/1.0")))
|
||||||
|
_version = 1;
|
||||||
|
|
||||||
|
_temp = String();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool strContains(const String &src, const String &find, bool mindcase = true) {
|
||||||
|
int pos=0, i=0;
|
||||||
|
const int slen = src.length();
|
||||||
|
const int flen = find.length();
|
||||||
|
|
||||||
|
if (slen < flen) return false;
|
||||||
|
while (pos <= (slen - flen)) {
|
||||||
|
for (i=0; i < flen; i++) {
|
||||||
|
if (mindcase) {
|
||||||
|
if (src[pos+i] != find[i]) i = flen + 1; // no match
|
||||||
|
}
|
||||||
|
else if (tolower(src[pos+i]) != tolower(find[i])) {
|
||||||
|
i = flen + 1; // no match
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (i == flen) return true;
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AsyncWebServerRequest::_parseReqHeader(){
|
||||||
|
int index = _temp.indexOf(':');
|
||||||
|
if(index){
|
||||||
|
String name = _temp.substring(0, index);
|
||||||
|
String value = _temp.substring(index + 2);
|
||||||
|
if(name.equalsIgnoreCase("Host")){
|
||||||
|
_host = value;
|
||||||
|
} else if(name.equalsIgnoreCase(F("Content-Type"))){
|
||||||
|
_contentType = value.substring(0, value.indexOf(';'));
|
||||||
|
if (value.startsWith(F("multipart/"))){
|
||||||
|
_boundary = value.substring(value.indexOf('=')+1);
|
||||||
|
_boundary.replace(String('"'), String());
|
||||||
|
_isMultipart = true;
|
||||||
|
}
|
||||||
|
} else if(name.equalsIgnoreCase(F("Content-Length"))){
|
||||||
|
_contentLength = atoi(value.c_str());
|
||||||
|
} else if(name.equalsIgnoreCase(F("Expect")) && value == F("100-continue")){
|
||||||
|
_expectingContinue = true;
|
||||||
|
} else if(name.equalsIgnoreCase(F("Authorization"))){
|
||||||
|
if(value.length() > 5 && value.substring(0,5).equalsIgnoreCase(F("Basic"))){
|
||||||
|
_authorization = value.substring(6);
|
||||||
|
} else if(value.length() > 6 && value.substring(0,6).equalsIgnoreCase(F("Digest"))){
|
||||||
|
_isDigest = true;
|
||||||
|
_authorization = value.substring(7);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(name.equalsIgnoreCase(F("Upgrade")) && value.equalsIgnoreCase(F("websocket"))){
|
||||||
|
// WebSocket request can be uniquely identified by header: [Upgrade: websocket]
|
||||||
|
_reqconntype = RCT_WS;
|
||||||
|
} else {
|
||||||
|
if(name.equalsIgnoreCase(F("Accept")) && strContains(value, F("text/event-stream"), false)){
|
||||||
|
// WebEvent request can be uniquely identified by header: [Accept: text/event-stream]
|
||||||
|
_reqconntype = RCT_EVENT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_headers.emplace_back(name, value);
|
||||||
|
}
|
||||||
|
_temp = String();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncWebServerRequest::_parsePlainPostChar(uint8_t data){
|
||||||
|
if(data && (char)data != '&')
|
||||||
|
_temp += (char)data;
|
||||||
|
if(!data || (char)data == '&' || _parsedLength == _contentLength){
|
||||||
|
String name = F("body");
|
||||||
|
String value = _temp;
|
||||||
|
if(!_temp.startsWith(String('{')) && !_temp.startsWith(String('[')) && _temp.indexOf('=') > 0){
|
||||||
|
name = _temp.substring(0, _temp.indexOf('='));
|
||||||
|
value = _temp.substring(_temp.indexOf('=') + 1);
|
||||||
|
}
|
||||||
|
_addParam(new AsyncWebParameter(urlDecode(name), urlDecode(value), true));
|
||||||
|
_temp = String();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncWebServerRequest::_handleUploadByte(uint8_t data, bool last){
|
||||||
|
_itemBuffer[_itemBufferIndex++] = data;
|
||||||
|
|
||||||
|
if(last || _itemBufferIndex == 1460){
|
||||||
|
//check if authenticated before calling the upload
|
||||||
|
if(_handler)
|
||||||
|
_handler->handleUpload(this, _itemFilename, _itemSize - _itemBufferIndex, _itemBuffer, _itemBufferIndex, false);
|
||||||
|
_itemBufferIndex = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum {
|
||||||
|
EXPECT_BOUNDARY,
|
||||||
|
PARSE_HEADERS,
|
||||||
|
WAIT_FOR_RETURN1,
|
||||||
|
EXPECT_FEED1,
|
||||||
|
EXPECT_DASH1,
|
||||||
|
EXPECT_DASH2,
|
||||||
|
BOUNDARY_OR_DATA,
|
||||||
|
DASH3_OR_RETURN2,
|
||||||
|
EXPECT_FEED2,
|
||||||
|
PARSING_FINISHED,
|
||||||
|
PARSE_ERROR
|
||||||
|
};
|
||||||
|
|
||||||
|
void AsyncWebServerRequest::_parseMultipartPostByte(uint8_t data, bool last){
|
||||||
|
#define itemWriteByte(b) do { _itemSize++; if(_itemIsFile) _handleUploadByte(b, last); else _itemValue+=(char)(b); } while(0)
|
||||||
|
|
||||||
|
if(!_parsedLength){
|
||||||
|
_multiParseState = EXPECT_BOUNDARY;
|
||||||
|
_temp = String();
|
||||||
|
_itemName = String();
|
||||||
|
_itemFilename = String();
|
||||||
|
_itemType = String();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(_multiParseState == WAIT_FOR_RETURN1){
|
||||||
|
if(data != '\r'){
|
||||||
|
itemWriteByte(data);
|
||||||
|
} else {
|
||||||
|
_multiParseState = EXPECT_FEED1;
|
||||||
|
}
|
||||||
|
} else if(_multiParseState == EXPECT_BOUNDARY){
|
||||||
|
if(_parsedLength < 2 && data != '-'){
|
||||||
|
_multiParseState = PARSE_ERROR;
|
||||||
|
return;
|
||||||
|
} else if(_parsedLength - 2 < _boundary.length() && _boundary.c_str()[_parsedLength - 2] != data){
|
||||||
|
_multiParseState = PARSE_ERROR;
|
||||||
|
return;
|
||||||
|
} else if(_parsedLength - 2 == _boundary.length() && data != '\r'){
|
||||||
|
_multiParseState = PARSE_ERROR;
|
||||||
|
return;
|
||||||
|
} else if(_parsedLength - 3 == _boundary.length()){
|
||||||
|
if(data != '\n'){
|
||||||
|
_multiParseState = PARSE_ERROR;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_multiParseState = PARSE_HEADERS;
|
||||||
|
_itemIsFile = false;
|
||||||
|
}
|
||||||
|
} else if(_multiParseState == PARSE_HEADERS){
|
||||||
|
if((char)data != '\r' && (char)data != '\n')
|
||||||
|
_temp += (char)data;
|
||||||
|
if((char)data == '\n'){
|
||||||
|
if(_temp.length()){
|
||||||
|
if(_temp.length() > 12 && _temp.substring(0, 12).equalsIgnoreCase(F("Content-Type"))){
|
||||||
|
_itemType = _temp.substring(14);
|
||||||
|
_itemIsFile = true;
|
||||||
|
} else if(_temp.length() > 19 && _temp.substring(0, 19).equalsIgnoreCase(F("Content-Disposition"))){
|
||||||
|
_temp = _temp.substring(_temp.indexOf(';') + 2);
|
||||||
|
while(_temp.indexOf(';') > 0){
|
||||||
|
String name = _temp.substring(0, _temp.indexOf('='));
|
||||||
|
String nameVal = _temp.substring(_temp.indexOf('=') + 2, _temp.indexOf(';') - 1);
|
||||||
|
if(name == F("name")){
|
||||||
|
_itemName = nameVal;
|
||||||
|
} else if(name == F("filename")){
|
||||||
|
_itemFilename = nameVal;
|
||||||
|
_itemIsFile = true;
|
||||||
|
}
|
||||||
|
_temp = _temp.substring(_temp.indexOf(';') + 2);
|
||||||
|
}
|
||||||
|
String name = _temp.substring(0, _temp.indexOf('='));
|
||||||
|
String nameVal = _temp.substring(_temp.indexOf('=') + 2, _temp.length() - 1);
|
||||||
|
if(name == F("name")){
|
||||||
|
_itemName = nameVal;
|
||||||
|
} else if(name == F("filename")){
|
||||||
|
_itemFilename = nameVal;
|
||||||
|
_itemIsFile = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_temp = String();
|
||||||
|
} else {
|
||||||
|
_multiParseState = WAIT_FOR_RETURN1;
|
||||||
|
//value starts from here
|
||||||
|
_itemSize = 0;
|
||||||
|
_itemStartIndex = _parsedLength;
|
||||||
|
_itemValue = String();
|
||||||
|
if(_itemIsFile){
|
||||||
|
if(_itemBuffer)
|
||||||
|
free(_itemBuffer);
|
||||||
|
_itemBuffer = (uint8_t*)malloc(1460);
|
||||||
|
if(_itemBuffer == NULL){
|
||||||
|
_multiParseState = PARSE_ERROR;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_itemBufferIndex = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if(_multiParseState == EXPECT_FEED1){
|
||||||
|
if(data != '\n'){
|
||||||
|
_multiParseState = WAIT_FOR_RETURN1;
|
||||||
|
itemWriteByte('\r'); _parseMultipartPostByte(data, last);
|
||||||
|
} else {
|
||||||
|
_multiParseState = EXPECT_DASH1;
|
||||||
|
}
|
||||||
|
} else if(_multiParseState == EXPECT_DASH1){
|
||||||
|
if(data != '-'){
|
||||||
|
_multiParseState = WAIT_FOR_RETURN1;
|
||||||
|
itemWriteByte('\r'); itemWriteByte('\n'); _parseMultipartPostByte(data, last);
|
||||||
|
} else {
|
||||||
|
_multiParseState = EXPECT_DASH2;
|
||||||
|
}
|
||||||
|
} else if(_multiParseState == EXPECT_DASH2){
|
||||||
|
if(data != '-'){
|
||||||
|
_multiParseState = WAIT_FOR_RETURN1;
|
||||||
|
itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); _parseMultipartPostByte(data, last);
|
||||||
|
} else {
|
||||||
|
_multiParseState = BOUNDARY_OR_DATA;
|
||||||
|
_boundaryPosition = 0;
|
||||||
|
}
|
||||||
|
} else if(_multiParseState == BOUNDARY_OR_DATA){
|
||||||
|
if(_boundaryPosition < _boundary.length() && _boundary.c_str()[_boundaryPosition] != data){
|
||||||
|
_multiParseState = WAIT_FOR_RETURN1;
|
||||||
|
itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); itemWriteByte('-');
|
||||||
|
uint8_t i;
|
||||||
|
for(i=0; i<_boundaryPosition; i++)
|
||||||
|
itemWriteByte(_boundary.c_str()[i]);
|
||||||
|
_parseMultipartPostByte(data, last);
|
||||||
|
} else if(_boundaryPosition == _boundary.length() - 1){
|
||||||
|
_multiParseState = DASH3_OR_RETURN2;
|
||||||
|
if(!_itemIsFile){
|
||||||
|
_addParam(new AsyncWebParameter(_itemName, _itemValue, true));
|
||||||
|
} else {
|
||||||
|
if(_itemSize){
|
||||||
|
//check if authenticated before calling the upload
|
||||||
|
if(_handler) _handler->handleUpload(this, _itemFilename, _itemSize - _itemBufferIndex, _itemBuffer, _itemBufferIndex, true);
|
||||||
|
_itemBufferIndex = 0;
|
||||||
|
_addParam(new AsyncWebParameter(_itemName, _itemFilename, true, true, _itemSize));
|
||||||
|
}
|
||||||
|
free(_itemBuffer);
|
||||||
|
_itemBuffer = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
_boundaryPosition++;
|
||||||
|
}
|
||||||
|
} else if(_multiParseState == DASH3_OR_RETURN2){
|
||||||
|
if(data == '-' && (_contentLength - _parsedLength - 4) != 0){
|
||||||
|
//os_printf("ERROR: The parser got to the end of the POST but is expecting %u bytes more!\nDrop an issue so we can have more info on the matter!\n", _contentLength - _parsedLength - 4);
|
||||||
|
_contentLength = _parsedLength + 4;//lets close the request gracefully
|
||||||
|
}
|
||||||
|
if(data == '\r'){
|
||||||
|
_multiParseState = EXPECT_FEED2;
|
||||||
|
} else if(data == '-' && _contentLength == (_parsedLength + 4)){
|
||||||
|
_multiParseState = PARSING_FINISHED;
|
||||||
|
} else {
|
||||||
|
_multiParseState = WAIT_FOR_RETURN1;
|
||||||
|
itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); itemWriteByte('-');
|
||||||
|
uint8_t i; for(i=0; i<_boundary.length(); i++) itemWriteByte(_boundary.c_str()[i]);
|
||||||
|
_parseMultipartPostByte(data, last);
|
||||||
|
}
|
||||||
|
} else if(_multiParseState == EXPECT_FEED2){
|
||||||
|
if(data == '\n'){
|
||||||
|
_multiParseState = PARSE_HEADERS;
|
||||||
|
_itemIsFile = false;
|
||||||
|
} else {
|
||||||
|
_multiParseState = WAIT_FOR_RETURN1;
|
||||||
|
itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); itemWriteByte('-');
|
||||||
|
uint8_t i; for(i=0; i<_boundary.length(); i++) itemWriteByte(_boundary.c_str()[i]);
|
||||||
|
itemWriteByte('\r'); _parseMultipartPostByte(data, last);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncWebServerRequest::_parseLine(){
|
||||||
|
if(_parseState == PARSE_REQ_START){
|
||||||
|
if(!_temp.length()){
|
||||||
|
_parseState = PARSE_REQ_FAIL;
|
||||||
|
_client->close();
|
||||||
|
} else {
|
||||||
|
_parseReqHead();
|
||||||
|
_parseState = PARSE_REQ_HEADERS;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(_parseState == PARSE_REQ_HEADERS){
|
||||||
|
if(!_temp.length()){
|
||||||
|
//end of headers
|
||||||
|
_server->_rewriteRequest(this);
|
||||||
|
_server->_attachHandler(this);
|
||||||
|
_removeNotInterestingHeaders();
|
||||||
|
if(_expectingContinue){
|
||||||
|
String response = F("HTTP/1.1 100 Continue\r\n\r\n");
|
||||||
|
_client->write(response.c_str(), response.length());
|
||||||
|
}
|
||||||
|
//check handler for authentication
|
||||||
|
if(_contentLength){
|
||||||
|
_parseState = PARSE_REQ_BODY;
|
||||||
|
} else {
|
||||||
|
_parseState = PARSE_REQ_END;
|
||||||
|
if(_handler) _handler->handleRequest(this);
|
||||||
|
else send(501);
|
||||||
|
}
|
||||||
|
} else _parseReqHeader();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t AsyncWebServerRequest::headers() const{
|
||||||
|
return _headers.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AsyncWebServerRequest::hasHeader(const String& name) const {
|
||||||
|
for(const auto& h: _headers){
|
||||||
|
if(h.name().equalsIgnoreCase(name)){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AsyncWebServerRequest::hasHeader(const __FlashStringHelper * data) const {
|
||||||
|
return hasHeader(String(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncWebHeader* AsyncWebServerRequest::getHeader(const String& name) {
|
||||||
|
auto iter = std::find_if(std::begin(_headers), std::end(_headers),
|
||||||
|
[&name](const AsyncWebHeader &header){ return header.name().equalsIgnoreCase(name); });
|
||||||
|
|
||||||
|
if (iter == std::end(_headers))
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
return &(*iter);
|
||||||
|
}
|
||||||
|
|
||||||
|
const AsyncWebHeader* AsyncWebServerRequest::getHeader(const String& name) const {
|
||||||
|
auto iter = std::find_if(std::begin(_headers), std::end(_headers),
|
||||||
|
[&name](const AsyncWebHeader &header){ return header.name().equalsIgnoreCase(name); });
|
||||||
|
|
||||||
|
if (iter == std::end(_headers))
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
return &(*iter);
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncWebHeader* AsyncWebServerRequest::getHeader(const __FlashStringHelper * data) {
|
||||||
|
PGM_P p = reinterpret_cast<PGM_P>(data);
|
||||||
|
size_t n = strlen_P(p);
|
||||||
|
char * name = (char*) malloc(n+1);
|
||||||
|
if (name) {
|
||||||
|
strcpy_P(name, p);
|
||||||
|
AsyncWebHeader* result = getHeader( String(name));
|
||||||
|
free(name);
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const AsyncWebHeader* AsyncWebServerRequest::getHeader(const __FlashStringHelper * data) const {
|
||||||
|
PGM_P p = reinterpret_cast<PGM_P>(data);
|
||||||
|
size_t n = strlen_P(p);
|
||||||
|
char * name = (char*) malloc(n+1);
|
||||||
|
if (name) {
|
||||||
|
strcpy_P(name, p);
|
||||||
|
const AsyncWebHeader* result = getHeader( String(name));
|
||||||
|
free(name);
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncWebHeader* AsyncWebServerRequest::getHeader(size_t num) {
|
||||||
|
if (num >= _headers.size())
|
||||||
|
return nullptr;
|
||||||
|
return &(*std::next(std::begin(_headers), num));
|
||||||
|
}
|
||||||
|
|
||||||
|
const AsyncWebHeader* AsyncWebServerRequest::getHeader(size_t num) const {
|
||||||
|
if (num >= _headers.size())
|
||||||
|
return nullptr;
|
||||||
|
return &(*std::next(std::begin(_headers), num));
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t AsyncWebServerRequest::params() const {
|
||||||
|
return _params.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AsyncWebServerRequest::hasParam(const String& name, bool post, bool file) const {
|
||||||
|
for(const auto& p: _params){
|
||||||
|
if(p->name() == name && p->isPost() == post && p->isFile() == file){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AsyncWebServerRequest::hasParam(const __FlashStringHelper * data, bool post, bool file) const {
|
||||||
|
return hasParam(String(data).c_str(), post, file);
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncWebParameter* AsyncWebServerRequest::getParam(const String& name, bool post, bool file) const {
|
||||||
|
for(const auto& p: _params){
|
||||||
|
if(p->name() == name && p->isPost() == post && p->isFile() == file){
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncWebParameter* AsyncWebServerRequest::getParam(const __FlashStringHelper * data, bool post, bool file) const {
|
||||||
|
return getParam(String(data).c_str(), post, file);
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncWebParameter* AsyncWebServerRequest::getParam(size_t num) const {
|
||||||
|
auto param = _params.nth(num);
|
||||||
|
return param ? *param : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncWebServerRequest::addInterestingHeader(const String& name){
|
||||||
|
if(std::none_of(std::begin(_interestingHeaders), std::end(_interestingHeaders),
|
||||||
|
[&name](const String &str){ return str.equalsIgnoreCase(name); }))
|
||||||
|
_interestingHeaders.push_back(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncWebServerRequest::send(AsyncWebServerResponse *response){
|
||||||
|
_response = response;
|
||||||
|
if(_response == NULL){
|
||||||
|
_client->close(true);
|
||||||
|
_onDisconnect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(!_response->_sourceValid()){
|
||||||
|
delete response;
|
||||||
|
_response = NULL;
|
||||||
|
send(500);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
_client->setRxTimeout(0);
|
||||||
|
_response->_respond(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(int code, const String& contentType, const String& content){
|
||||||
|
return new AsyncBasicResponse(code, contentType, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(FS &fs, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback){
|
||||||
|
if(fs.exists(path) || (!download && fs.exists(path+F(".gz"))))
|
||||||
|
return new AsyncFileResponse(fs, path, contentType, download, callback);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(File content, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback){
|
||||||
|
if(content == true)
|
||||||
|
return new AsyncFileResponse(content, path, contentType, download, callback);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback){
|
||||||
|
return new AsyncStreamResponse(stream, contentType, len, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback){
|
||||||
|
return new AsyncCallbackResponse(contentType, len, callback, templateCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncWebServerResponse * AsyncWebServerRequest::beginChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback){
|
||||||
|
if(_version)
|
||||||
|
return new AsyncChunkedResponse(contentType, callback, templateCallback);
|
||||||
|
return new AsyncCallbackResponse(contentType, 0, callback, templateCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncResponseStream * AsyncWebServerRequest::beginResponseStream(const String& contentType, size_t bufferSize){
|
||||||
|
return new AsyncResponseStream(contentType, bufferSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncWebServerResponse * AsyncWebServerRequest::beginResponse_P(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback){
|
||||||
|
return new AsyncProgmemResponse(code, contentType, content, len, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncWebServerResponse * AsyncWebServerRequest::beginResponse_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback){
|
||||||
|
return beginResponse_P(code, contentType, (const uint8_t *)content, strlen_P(content), callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncWebServerRequest::send(int code, const String& contentType, const String& content){
|
||||||
|
send(beginResponse(code, contentType, content));
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncWebServerRequest::send(FS &fs, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback){
|
||||||
|
if(fs.exists(path) || (!download && fs.exists(path+F(".gz")))){
|
||||||
|
send(beginResponse(fs, path, contentType, download, callback));
|
||||||
|
} else send(404);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncWebServerRequest::send(File content, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback){
|
||||||
|
if(content == true){
|
||||||
|
send(beginResponse(content, path, contentType, download, callback));
|
||||||
|
} else send(404);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncWebServerRequest::send(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback){
|
||||||
|
send(beginResponse(stream, contentType, len, callback));
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncWebServerRequest::send(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback){
|
||||||
|
send(beginResponse(contentType, len, callback, templateCallback));
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncWebServerRequest::sendChunked(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback){
|
||||||
|
send(beginChunkedResponse(contentType, callback, templateCallback));
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncWebServerRequest::send_P(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback){
|
||||||
|
send(beginResponse_P(code, contentType, content, len, callback));
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncWebServerRequest::send_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback){
|
||||||
|
send(beginResponse_P(code, contentType, content, callback));
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncWebServerRequest::redirect(const String& url){
|
||||||
|
AsyncWebServerResponse * response = beginResponse(302);
|
||||||
|
response->addHeader(F("Location"), url);
|
||||||
|
send(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AsyncWebServerRequest::authenticate(const char * username, const char * password, const char * realm, bool passwordIsHash){
|
||||||
|
if(_authorization.length()){
|
||||||
|
if(_isDigest)
|
||||||
|
return checkDigestAuthentication(_authorization.c_str(), methodToString(), username, password, realm, passwordIsHash, NULL, NULL, NULL);
|
||||||
|
else if(!passwordIsHash)
|
||||||
|
return checkBasicAuthentication(_authorization.c_str(), username, password);
|
||||||
|
else
|
||||||
|
return _authorization.equals(password);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AsyncWebServerRequest::authenticate(const char * hash){
|
||||||
|
if(!_authorization.length() || hash == NULL)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if(_isDigest){
|
||||||
|
String hStr = String(hash);
|
||||||
|
int separator = hStr.indexOf(':');
|
||||||
|
if(separator <= 0)
|
||||||
|
return false;
|
||||||
|
String username = hStr.substring(0, separator);
|
||||||
|
hStr = hStr.substring(separator + 1);
|
||||||
|
separator = hStr.indexOf(':');
|
||||||
|
if(separator <= 0)
|
||||||
|
return false;
|
||||||
|
String realm = hStr.substring(0, separator);
|
||||||
|
hStr = hStr.substring(separator + 1);
|
||||||
|
return checkDigestAuthentication(_authorization.c_str(), methodToString(), username.c_str(), hStr.c_str(), realm.c_str(), true, NULL, NULL, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (_authorization.equals(hash));
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncWebServerRequest::requestAuthentication(const char * realm, bool isDigest){
|
||||||
|
AsyncWebServerResponse * r = beginResponse(401);
|
||||||
|
if(!isDigest && realm == NULL){
|
||||||
|
r->addHeader(F("WWW-Authenticate"), F("Basic realm=\"Login Required\""));
|
||||||
|
} else if(!isDigest){
|
||||||
|
String header = F("Basic realm=\"");
|
||||||
|
header.concat(realm);
|
||||||
|
header += '"';
|
||||||
|
r->addHeader(F("WWW-Authenticate"), header);
|
||||||
|
} else {
|
||||||
|
String header = F("Digest ");
|
||||||
|
header.concat(requestDigestAuthentication(realm));
|
||||||
|
r->addHeader(F("WWW-Authenticate"), header);
|
||||||
|
}
|
||||||
|
send(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AsyncWebServerRequest::hasArg(const char* name) const {
|
||||||
|
for(const auto& arg: _params){
|
||||||
|
if(arg->name() == name){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AsyncWebServerRequest::hasArg(const __FlashStringHelper * data) const {
|
||||||
|
return hasArg(String(data).c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const String& AsyncWebServerRequest::arg(const String& name) const {
|
||||||
|
for(const auto& arg: _params){
|
||||||
|
if(arg->name() == name){
|
||||||
|
return arg->value();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return emptyString;
|
||||||
|
}
|
||||||
|
|
||||||
|
const String& AsyncWebServerRequest::arg(const __FlashStringHelper * data) const {
|
||||||
|
return arg(String(data).c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
const String& AsyncWebServerRequest::arg(size_t i) const {
|
||||||
|
return getParam(i)->value();
|
||||||
|
}
|
||||||
|
|
||||||
|
const String& AsyncWebServerRequest::argName(size_t i) const {
|
||||||
|
return getParam(i)->name();
|
||||||
|
}
|
||||||
|
|
||||||
|
const String& AsyncWebServerRequest::pathArg(size_t i) const {
|
||||||
|
return i < _pathParams.size() ? _pathParams[i] : emptyString;
|
||||||
|
}
|
||||||
|
|
||||||
|
const String& AsyncWebServerRequest::header(const char* name) const {
|
||||||
|
const AsyncWebHeader* h = getHeader(String(name));
|
||||||
|
return h ? h->value() : emptyString;
|
||||||
|
}
|
||||||
|
|
||||||
|
const String& AsyncWebServerRequest::header(const __FlashStringHelper * data) const {
|
||||||
|
return header(String(data).c_str());
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const String& AsyncWebServerRequest::header(size_t i) const {
|
||||||
|
const AsyncWebHeader* h = getHeader(i);
|
||||||
|
return h ? h->value() : emptyString;
|
||||||
|
}
|
||||||
|
|
||||||
|
const String& AsyncWebServerRequest::headerName(size_t i) const {
|
||||||
|
const AsyncWebHeader* h = getHeader(i);
|
||||||
|
return h ? h->name() : emptyString;
|
||||||
|
}
|
||||||
|
|
||||||
|
String AsyncWebServerRequest::urlDecode(const String& text) const {
|
||||||
|
char temp[] = "0x00";
|
||||||
|
unsigned int len = text.length();
|
||||||
|
unsigned int i = 0;
|
||||||
|
String decoded = String();
|
||||||
|
decoded.reserve(len); // Allocate the string internal buffer - never longer from source text
|
||||||
|
while (i < len){
|
||||||
|
char decodedChar;
|
||||||
|
char encodedChar = text.charAt(i++);
|
||||||
|
if ((encodedChar == '%') && (i + 1 < len)){
|
||||||
|
temp[2] = text.charAt(i++);
|
||||||
|
temp[3] = text.charAt(i++);
|
||||||
|
decodedChar = strtol(temp, NULL, 16);
|
||||||
|
} else if (encodedChar == '+') {
|
||||||
|
decodedChar = ' ';
|
||||||
|
} else {
|
||||||
|
decodedChar = encodedChar; // normal ascii char
|
||||||
|
}
|
||||||
|
decoded.concat(decodedChar);
|
||||||
|
}
|
||||||
|
return decoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const __FlashStringHelper *AsyncWebServerRequest::methodToString() const {
|
||||||
|
if(_method == HTTP_ANY) return F("ANY");
|
||||||
|
else if(_method & HTTP_GET) return F("GET");
|
||||||
|
else if(_method & HTTP_POST) return F("POST");
|
||||||
|
else if(_method & HTTP_DELETE) return F("DELETE");
|
||||||
|
else if(_method & HTTP_PUT) return F("PUT");
|
||||||
|
else if(_method & HTTP_PATCH) return F("PATCH");
|
||||||
|
else if(_method & HTTP_HEAD) return F("HEAD");
|
||||||
|
else if(_method & HTTP_OPTIONS) return F("OPTIONS");
|
||||||
|
return F("UNKNOWN");
|
||||||
|
}
|
||||||
|
|
||||||
|
const __FlashStringHelper *AsyncWebServerRequest::requestedConnTypeToString() const {
|
||||||
|
switch (_reqconntype) {
|
||||||
|
case RCT_NOT_USED: return F("RCT_NOT_USED");
|
||||||
|
case RCT_DEFAULT: return F("RCT_DEFAULT");
|
||||||
|
case RCT_HTTP: return F("RCT_HTTP");
|
||||||
|
case RCT_WS: return F("RCT_WS");
|
||||||
|
case RCT_EVENT: return F("RCT_EVENT");
|
||||||
|
default: return F("ERROR");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AsyncWebServerRequest::isExpectedRequestedConnType(RequestedConnectionType erct1, RequestedConnectionType erct2, RequestedConnectionType erct3) {
|
||||||
|
bool res = false;
|
||||||
|
if ((erct1 != RCT_NOT_USED) && (erct1 == _reqconntype)) res = true;
|
||||||
|
if ((erct2 != RCT_NOT_USED) && (erct2 == _reqconntype)) res = true;
|
||||||
|
if ((erct3 != RCT_NOT_USED) && (erct3 == _reqconntype)) res = true;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
138
libraries/ESPAsyncWebServer/src/WebResponseImpl.h
Normal file
138
libraries/ESPAsyncWebServer/src/WebResponseImpl.h
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
/*
|
||||||
|
Asynchronous WebServer library for Espressif MCUs
|
||||||
|
|
||||||
|
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
|
||||||
|
This file is part of the esp8266 core for Arduino environment.
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
#ifndef ASYNCWEBSERVERRESPONSEIMPL_H_
|
||||||
|
#define ASYNCWEBSERVERRESPONSEIMPL_H_
|
||||||
|
|
||||||
|
#ifdef Arduino_h
|
||||||
|
// arduino is not compatible with std::vector
|
||||||
|
#undef min
|
||||||
|
#undef max
|
||||||
|
#endif
|
||||||
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
// It is possible to restore these defines, but one can use _min and _max instead. Or std::min, std::max.
|
||||||
|
|
||||||
|
class AsyncBasicResponse: public AsyncWebServerResponse {
|
||||||
|
private:
|
||||||
|
String _content;
|
||||||
|
public:
|
||||||
|
AsyncBasicResponse(int code, const String& contentType=String(), const String& content=String());
|
||||||
|
void _respond(AsyncWebServerRequest *request);
|
||||||
|
size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time);
|
||||||
|
bool _sourceValid() const { return true; }
|
||||||
|
};
|
||||||
|
|
||||||
|
class AsyncAbstractResponse: public AsyncWebServerResponse {
|
||||||
|
private:
|
||||||
|
String _head;
|
||||||
|
// Data is inserted into cache at begin().
|
||||||
|
// This is inefficient with vector, but if we use some other container,
|
||||||
|
// we won't be able to access it as contiguous array of bytes when reading from it,
|
||||||
|
// so by gaining performance in one place, we'll lose it in another.
|
||||||
|
std::vector<uint8_t> _cache;
|
||||||
|
size_t _readDataFromCacheOrContent(uint8_t* data, const size_t len);
|
||||||
|
size_t _fillBufferAndProcessTemplates(uint8_t* buf, size_t maxLen);
|
||||||
|
protected:
|
||||||
|
AwsTemplateProcessor _callback;
|
||||||
|
public:
|
||||||
|
AsyncAbstractResponse(AwsTemplateProcessor callback=nullptr);
|
||||||
|
void _respond(AsyncWebServerRequest *request);
|
||||||
|
size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time);
|
||||||
|
bool _sourceValid() const { return false; }
|
||||||
|
virtual size_t _fillBuffer(uint8_t *buf __attribute__((unused)), size_t maxLen __attribute__((unused))) { return 0; }
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifndef TEMPLATE_PLACEHOLDER
|
||||||
|
#define TEMPLATE_PLACEHOLDER '%'
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define TEMPLATE_PARAM_NAME_LENGTH 32
|
||||||
|
class AsyncFileResponse: public AsyncAbstractResponse {
|
||||||
|
using File = fs::File;
|
||||||
|
using FS = fs::FS;
|
||||||
|
private:
|
||||||
|
File _content;
|
||||||
|
String _path;
|
||||||
|
void _setContentType(const String& path);
|
||||||
|
public:
|
||||||
|
AsyncFileResponse(FS &fs, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr);
|
||||||
|
AsyncFileResponse(File content, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr);
|
||||||
|
~AsyncFileResponse();
|
||||||
|
bool _sourceValid() const { return !!(_content); }
|
||||||
|
virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class AsyncStreamResponse: public AsyncAbstractResponse {
|
||||||
|
private:
|
||||||
|
Stream *_content;
|
||||||
|
public:
|
||||||
|
AsyncStreamResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback=nullptr);
|
||||||
|
bool _sourceValid() const { return !!(_content); }
|
||||||
|
virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class AsyncCallbackResponse: public AsyncAbstractResponse {
|
||||||
|
private:
|
||||||
|
AwsResponseFiller _content;
|
||||||
|
size_t _filledLength;
|
||||||
|
public:
|
||||||
|
AsyncCallbackResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr);
|
||||||
|
bool _sourceValid() const { return !!(_content); }
|
||||||
|
virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class AsyncChunkedResponse: public AsyncAbstractResponse {
|
||||||
|
private:
|
||||||
|
AwsResponseFiller _content;
|
||||||
|
size_t _filledLength;
|
||||||
|
public:
|
||||||
|
AsyncChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr);
|
||||||
|
bool _sourceValid() const { return !!(_content); }
|
||||||
|
virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class AsyncProgmemResponse: public AsyncAbstractResponse {
|
||||||
|
private:
|
||||||
|
const uint8_t * _content;
|
||||||
|
size_t _readLength;
|
||||||
|
public:
|
||||||
|
AsyncProgmemResponse(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback=nullptr);
|
||||||
|
bool _sourceValid() const { return true; }
|
||||||
|
virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class cbuf;
|
||||||
|
|
||||||
|
class AsyncResponseStream: public AsyncAbstractResponse, public Print {
|
||||||
|
private:
|
||||||
|
std::unique_ptr<cbuf> _content;
|
||||||
|
public:
|
||||||
|
AsyncResponseStream(const String& contentType, size_t bufferSize);
|
||||||
|
~AsyncResponseStream();
|
||||||
|
bool _sourceValid() const { return (_state < RESPONSE_END); }
|
||||||
|
virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override;
|
||||||
|
size_t write(const uint8_t *data, size_t len);
|
||||||
|
size_t write(uint8_t data);
|
||||||
|
using Print::write;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* ASYNCWEBSERVERRESPONSEIMPL_H_ */
|
||||||
704
libraries/ESPAsyncWebServer/src/WebResponses.cpp
Normal file
704
libraries/ESPAsyncWebServer/src/WebResponses.cpp
Normal file
@@ -0,0 +1,704 @@
|
|||||||
|
/*
|
||||||
|
Asynchronous WebServer library for Espressif MCUs
|
||||||
|
|
||||||
|
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
|
||||||
|
This file is part of the esp8266 core for Arduino environment.
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
#include "ESPAsyncWebServer.h"
|
||||||
|
#include "WebResponseImpl.h"
|
||||||
|
#include "cbuf.h"
|
||||||
|
|
||||||
|
// Since ESP8266 does not link memchr by default, here's its implementation.
|
||||||
|
void* memchr(void* ptr, int ch, size_t count)
|
||||||
|
{
|
||||||
|
unsigned char* p = static_cast<unsigned char*>(ptr);
|
||||||
|
while(count--)
|
||||||
|
if(*p++ == static_cast<unsigned char>(ch))
|
||||||
|
return --p;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Abstract Response
|
||||||
|
* */
|
||||||
|
const char* AsyncWebServerResponse::_responseCodeToString(int code) {
|
||||||
|
return reinterpret_cast<const char *>(responseCodeToString(code));
|
||||||
|
}
|
||||||
|
|
||||||
|
const __FlashStringHelper *AsyncWebServerResponse::responseCodeToString(int code) {
|
||||||
|
switch (code) {
|
||||||
|
case 100: return F("Continue");
|
||||||
|
case 101: return F("Switching Protocols");
|
||||||
|
case 200: return F("OK");
|
||||||
|
case 201: return F("Created");
|
||||||
|
case 202: return F("Accepted");
|
||||||
|
case 203: return F("Non-Authoritative Information");
|
||||||
|
case 204: return F("No Content");
|
||||||
|
case 205: return F("Reset Content");
|
||||||
|
case 206: return F("Partial Content");
|
||||||
|
case 300: return F("Multiple Choices");
|
||||||
|
case 301: return F("Moved Permanently");
|
||||||
|
case 302: return F("Found");
|
||||||
|
case 303: return F("See Other");
|
||||||
|
case 304: return F("Not Modified");
|
||||||
|
case 305: return F("Use Proxy");
|
||||||
|
case 307: return F("Temporary Redirect");
|
||||||
|
case 400: return F("Bad Request");
|
||||||
|
case 401: return F("Unauthorized");
|
||||||
|
case 402: return F("Payment Required");
|
||||||
|
case 403: return F("Forbidden");
|
||||||
|
case 404: return F("Not Found");
|
||||||
|
case 405: return F("Method Not Allowed");
|
||||||
|
case 406: return F("Not Acceptable");
|
||||||
|
case 407: return F("Proxy Authentication Required");
|
||||||
|
case 408: return F("Request Time-out");
|
||||||
|
case 409: return F("Conflict");
|
||||||
|
case 410: return F("Gone");
|
||||||
|
case 411: return F("Length Required");
|
||||||
|
case 412: return F("Precondition Failed");
|
||||||
|
case 413: return F("Request Entity Too Large");
|
||||||
|
case 414: return F("Request-URI Too Large");
|
||||||
|
case 415: return F("Unsupported Media Type");
|
||||||
|
case 416: return F("Requested range not satisfiable");
|
||||||
|
case 417: return F("Expectation Failed");
|
||||||
|
case 500: return F("Internal Server Error");
|
||||||
|
case 501: return F("Not Implemented");
|
||||||
|
case 502: return F("Bad Gateway");
|
||||||
|
case 503: return F("Service Unavailable");
|
||||||
|
case 504: return F("Gateway Time-out");
|
||||||
|
case 505: return F("HTTP Version not supported");
|
||||||
|
default: return F("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncWebServerResponse::AsyncWebServerResponse()
|
||||||
|
: _code(0)
|
||||||
|
, _contentType()
|
||||||
|
, _contentLength(0)
|
||||||
|
, _sendContentLength(true)
|
||||||
|
, _chunked(false)
|
||||||
|
, _headLength(0)
|
||||||
|
, _sentLength(0)
|
||||||
|
, _ackedLength(0)
|
||||||
|
, _writtenLength(0)
|
||||||
|
, _state(RESPONSE_SETUP)
|
||||||
|
{
|
||||||
|
for(const auto &header: DefaultHeaders::Instance()) {
|
||||||
|
_headers.emplace_back(header);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncWebServerResponse::~AsyncWebServerResponse() = default;
|
||||||
|
|
||||||
|
void AsyncWebServerResponse::setCode(int code){
|
||||||
|
if(_state == RESPONSE_SETUP)
|
||||||
|
_code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncWebServerResponse::setContentLength(size_t len){
|
||||||
|
if(_state == RESPONSE_SETUP)
|
||||||
|
_contentLength = len;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncWebServerResponse::setContentType(const String& type){
|
||||||
|
if(_state == RESPONSE_SETUP)
|
||||||
|
_contentType = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncWebServerResponse::addHeader(const String& name, const String& value){
|
||||||
|
_headers.emplace_back(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
String AsyncWebServerResponse::_assembleHead(uint8_t version){
|
||||||
|
if(version){
|
||||||
|
addHeader(F("Accept-Ranges"), F("none"));
|
||||||
|
if(_chunked)
|
||||||
|
addHeader(F("Transfer-Encoding"), F("chunked"));
|
||||||
|
}
|
||||||
|
String out = String();
|
||||||
|
int bufSize = 300;
|
||||||
|
char buf[bufSize];
|
||||||
|
|
||||||
|
snprintf_P(buf, bufSize, PSTR("HTTP/1.%d %d %s\r\n"), version, _code, _responseCodeToString(_code));
|
||||||
|
out.concat(buf);
|
||||||
|
|
||||||
|
if(_sendContentLength) {
|
||||||
|
snprintf_P(buf, bufSize, PSTR("Content-Length: %d\r\n"), _contentLength);
|
||||||
|
out.concat(buf);
|
||||||
|
}
|
||||||
|
if(_contentType.length()) {
|
||||||
|
snprintf_P(buf, bufSize, PSTR("Content-Type: %s\r\n"), _contentType.c_str());
|
||||||
|
out.concat(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const auto& header: _headers){
|
||||||
|
snprintf_P(buf, bufSize, PSTR("%s: %s\r\n"), header.name().c_str(), header.value().c_str());
|
||||||
|
out.concat(buf);
|
||||||
|
}
|
||||||
|
_headers.clear();
|
||||||
|
|
||||||
|
out.concat(F("\r\n"));
|
||||||
|
_headLength = out.length();
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AsyncWebServerResponse::_started() const { return _state > RESPONSE_SETUP; }
|
||||||
|
bool AsyncWebServerResponse::_finished() const { return _state > RESPONSE_WAIT_ACK; }
|
||||||
|
bool AsyncWebServerResponse::_failed() const { return _state == RESPONSE_FAILED; }
|
||||||
|
bool AsyncWebServerResponse::_sourceValid() const { return false; }
|
||||||
|
void AsyncWebServerResponse::_respond(AsyncWebServerRequest *request){ _state = RESPONSE_END; request->client()->close(); }
|
||||||
|
size_t AsyncWebServerResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){ (void)request; (void)len; (void)time; return 0; }
|
||||||
|
|
||||||
|
/*
|
||||||
|
* String/Code Response
|
||||||
|
* */
|
||||||
|
AsyncBasicResponse::AsyncBasicResponse(int code, const String& contentType, const String& content){
|
||||||
|
_code = code;
|
||||||
|
_content = content;
|
||||||
|
_contentType = contentType;
|
||||||
|
if(_content.length()){
|
||||||
|
_contentLength = _content.length();
|
||||||
|
if(!_contentType.length())
|
||||||
|
_contentType = F("text/plain");
|
||||||
|
}
|
||||||
|
addHeader(F("Connection"), F("close"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncBasicResponse::_respond(AsyncWebServerRequest *request){
|
||||||
|
_state = RESPONSE_HEADERS;
|
||||||
|
String out = _assembleHead(request->version());
|
||||||
|
size_t outLen = out.length();
|
||||||
|
size_t space = request->client()->space();
|
||||||
|
if(!_contentLength && space >= outLen){
|
||||||
|
_writtenLength += request->client()->write(out.c_str(), outLen);
|
||||||
|
_state = RESPONSE_WAIT_ACK;
|
||||||
|
} else if(_contentLength && space >= outLen + _contentLength){
|
||||||
|
out += _content;
|
||||||
|
outLen += _contentLength;
|
||||||
|
_writtenLength += request->client()->write(out.c_str(), outLen);
|
||||||
|
_state = RESPONSE_WAIT_ACK;
|
||||||
|
} else if(space && space < outLen){
|
||||||
|
String partial = out.substring(0, space);
|
||||||
|
_content = out.substring(space) + _content;
|
||||||
|
_contentLength += outLen - space;
|
||||||
|
_writtenLength += request->client()->write(partial.c_str(), partial.length());
|
||||||
|
_state = RESPONSE_CONTENT;
|
||||||
|
} else if(space > outLen && space < (outLen + _contentLength)){
|
||||||
|
size_t shift = space - outLen;
|
||||||
|
outLen += shift;
|
||||||
|
_sentLength += shift;
|
||||||
|
out += _content.substring(0, shift);
|
||||||
|
_content = _content.substring(shift);
|
||||||
|
_writtenLength += request->client()->write(out.c_str(), outLen);
|
||||||
|
_state = RESPONSE_CONTENT;
|
||||||
|
} else {
|
||||||
|
_content = out + _content;
|
||||||
|
_contentLength += outLen;
|
||||||
|
_state = RESPONSE_CONTENT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t AsyncBasicResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){
|
||||||
|
(void)time;
|
||||||
|
_ackedLength += len;
|
||||||
|
if(_state == RESPONSE_CONTENT){
|
||||||
|
size_t available = _contentLength - _sentLength;
|
||||||
|
size_t space = request->client()->space();
|
||||||
|
//we can fit in this packet
|
||||||
|
if(space > available){
|
||||||
|
_writtenLength += request->client()->write(_content.c_str(), available);
|
||||||
|
_content = String();
|
||||||
|
_state = RESPONSE_WAIT_ACK;
|
||||||
|
return available;
|
||||||
|
}
|
||||||
|
//send some data, the rest on ack
|
||||||
|
String out = _content.substring(0, space);
|
||||||
|
_content = _content.substring(space);
|
||||||
|
_sentLength += space;
|
||||||
|
_writtenLength += request->client()->write(out.c_str(), space);
|
||||||
|
return space;
|
||||||
|
} else if(_state == RESPONSE_WAIT_ACK){
|
||||||
|
if(_ackedLength >= _writtenLength){
|
||||||
|
_state = RESPONSE_END;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Abstract Response
|
||||||
|
* */
|
||||||
|
|
||||||
|
AsyncAbstractResponse::AsyncAbstractResponse(AwsTemplateProcessor callback): _callback(callback)
|
||||||
|
{
|
||||||
|
// In case of template processing, we're unable to determine real response size
|
||||||
|
if(callback) {
|
||||||
|
_contentLength = 0;
|
||||||
|
_sendContentLength = false;
|
||||||
|
_chunked = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncAbstractResponse::_respond(AsyncWebServerRequest *request){
|
||||||
|
addHeader(F("Connection"), F("close"));
|
||||||
|
_head = _assembleHead(request->version());
|
||||||
|
_state = RESPONSE_HEADERS;
|
||||||
|
_ack(request, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){
|
||||||
|
(void)time;
|
||||||
|
if(!_sourceValid()){
|
||||||
|
_state = RESPONSE_FAILED;
|
||||||
|
request->client()->close();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
_ackedLength += len;
|
||||||
|
size_t space = request->client()->space();
|
||||||
|
|
||||||
|
size_t headLen = _head.length();
|
||||||
|
if(_state == RESPONSE_HEADERS){
|
||||||
|
if(space >= headLen){
|
||||||
|
_state = RESPONSE_CONTENT;
|
||||||
|
space -= headLen;
|
||||||
|
} else {
|
||||||
|
String out = _head.substring(0, space);
|
||||||
|
_head = _head.substring(space);
|
||||||
|
_writtenLength += request->client()->write(out.c_str(), out.length());
|
||||||
|
return out.length();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(_state == RESPONSE_CONTENT){
|
||||||
|
size_t outLen;
|
||||||
|
if(_chunked){
|
||||||
|
if(space <= 8){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
outLen = space;
|
||||||
|
} else if(!_sendContentLength){
|
||||||
|
outLen = space;
|
||||||
|
} else {
|
||||||
|
outLen = ((_contentLength - _sentLength) > space)?space:(_contentLength - _sentLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t *buf = (uint8_t *)malloc(outLen+headLen);
|
||||||
|
if (!buf) {
|
||||||
|
// os_printf("_ack malloc %d failed\n", outLen+headLen);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(headLen){
|
||||||
|
memcpy(buf, _head.c_str(), _head.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t readLen = 0;
|
||||||
|
|
||||||
|
if(_chunked){
|
||||||
|
// HTTP 1.1 allows leading zeros in chunk length. Or spaces may be added.
|
||||||
|
// See RFC2616 sections 2, 3.6.1.
|
||||||
|
readLen = _fillBufferAndProcessTemplates(buf+headLen+6, outLen - 8);
|
||||||
|
if(readLen == RESPONSE_TRY_AGAIN){
|
||||||
|
free(buf);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
outLen = sprintf_P((char*)buf+headLen, PSTR("%x"), readLen) + headLen;
|
||||||
|
while(outLen < headLen + 4) buf[outLen++] = ' ';
|
||||||
|
buf[outLen++] = '\r';
|
||||||
|
buf[outLen++] = '\n';
|
||||||
|
outLen += readLen;
|
||||||
|
buf[outLen++] = '\r';
|
||||||
|
buf[outLen++] = '\n';
|
||||||
|
} else {
|
||||||
|
readLen = _fillBufferAndProcessTemplates(buf+headLen, outLen);
|
||||||
|
if(readLen == RESPONSE_TRY_AGAIN){
|
||||||
|
free(buf);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
outLen = readLen + headLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(headLen){
|
||||||
|
_head = String();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(outLen){
|
||||||
|
_writtenLength += request->client()->write((const char*)buf, outLen);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(_chunked){
|
||||||
|
_sentLength += readLen;
|
||||||
|
} else {
|
||||||
|
_sentLength += outLen - headLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(buf);
|
||||||
|
|
||||||
|
if((_chunked && readLen == 0) || (!_sendContentLength && outLen == 0) || (!_chunked && _sentLength == _contentLength)){
|
||||||
|
_state = RESPONSE_WAIT_ACK;
|
||||||
|
}
|
||||||
|
return outLen;
|
||||||
|
|
||||||
|
} else if(_state == RESPONSE_WAIT_ACK){
|
||||||
|
if(!_sendContentLength || _ackedLength >= _writtenLength){
|
||||||
|
_state = RESPONSE_END;
|
||||||
|
if(!_chunked && !_sendContentLength)
|
||||||
|
request->client()->close(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t AsyncAbstractResponse::_readDataFromCacheOrContent(uint8_t* data, const size_t len)
|
||||||
|
{
|
||||||
|
// If we have something in cache, copy it to buffer
|
||||||
|
const size_t readFromCache = std::min(len, _cache.size());
|
||||||
|
if(readFromCache) {
|
||||||
|
memcpy(data, _cache.data(), readFromCache);
|
||||||
|
_cache.erase(_cache.begin(), _cache.begin() + readFromCache);
|
||||||
|
}
|
||||||
|
// If we need to read more...
|
||||||
|
const size_t needFromFile = len - readFromCache;
|
||||||
|
const size_t readFromContent = _fillBuffer(data + readFromCache, needFromFile);
|
||||||
|
return readFromCache + readFromContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size_t len)
|
||||||
|
{
|
||||||
|
if(!_callback)
|
||||||
|
return _fillBuffer(data, len);
|
||||||
|
|
||||||
|
const size_t originalLen = len;
|
||||||
|
len = _readDataFromCacheOrContent(data, len);
|
||||||
|
// Now we've read 'len' bytes, either from cache or from file
|
||||||
|
// Search for template placeholders
|
||||||
|
uint8_t* pTemplateStart = data;
|
||||||
|
while((pTemplateStart < &data[len]) && (pTemplateStart = (uint8_t*)memchr(pTemplateStart, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart + 1))) { // data[0] ... data[len - 1]
|
||||||
|
uint8_t* pTemplateEnd = (pTemplateStart < &data[len - 1]) ? (uint8_t*)memchr(pTemplateStart + 1, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart) : nullptr;
|
||||||
|
// temporary buffer to hold parameter name
|
||||||
|
uint8_t buf[TEMPLATE_PARAM_NAME_LENGTH + 1];
|
||||||
|
String paramName;
|
||||||
|
// If closing placeholder is found:
|
||||||
|
if(pTemplateEnd) {
|
||||||
|
// prepare argument to callback
|
||||||
|
const size_t paramNameLength = std::min((size_t)sizeof(buf) - 1, (size_t)(pTemplateEnd - pTemplateStart - 1));
|
||||||
|
if(paramNameLength) {
|
||||||
|
memcpy(buf, pTemplateStart + 1, paramNameLength);
|
||||||
|
buf[paramNameLength] = 0;
|
||||||
|
paramName = String(reinterpret_cast<char*>(buf));
|
||||||
|
} else { // double percent sign encountered, this is single percent sign escaped.
|
||||||
|
// remove the 2nd percent sign
|
||||||
|
memmove(pTemplateEnd, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1);
|
||||||
|
len += _readDataFromCacheOrContent(&data[len - 1], 1) - 1;
|
||||||
|
++pTemplateStart;
|
||||||
|
}
|
||||||
|
} else if(&data[len - 1] - pTemplateStart + 1 < TEMPLATE_PARAM_NAME_LENGTH + 2) { // closing placeholder not found, check if it's in the remaining file data
|
||||||
|
memcpy(buf, pTemplateStart + 1, &data[len - 1] - pTemplateStart);
|
||||||
|
const size_t readFromCacheOrContent = _readDataFromCacheOrContent(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PARAM_NAME_LENGTH + 2 - (&data[len - 1] - pTemplateStart + 1));
|
||||||
|
if(readFromCacheOrContent) {
|
||||||
|
pTemplateEnd = (uint8_t*)memchr(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PLACEHOLDER, readFromCacheOrContent);
|
||||||
|
if(pTemplateEnd) {
|
||||||
|
// prepare argument to callback
|
||||||
|
*pTemplateEnd = 0;
|
||||||
|
paramName = String(reinterpret_cast<char*>(buf));
|
||||||
|
// Copy remaining read-ahead data into cache
|
||||||
|
_cache.insert(_cache.begin(), pTemplateEnd + 1, buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent);
|
||||||
|
pTemplateEnd = &data[len - 1];
|
||||||
|
}
|
||||||
|
else // closing placeholder not found in file data, store found percent symbol as is and advance to the next position
|
||||||
|
{
|
||||||
|
// but first, store read file data in cache
|
||||||
|
_cache.insert(_cache.begin(), buf + (&data[len - 1] - pTemplateStart), buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent);
|
||||||
|
++pTemplateStart;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else // closing placeholder not found in content data, store found percent symbol as is and advance to the next position
|
||||||
|
++pTemplateStart;
|
||||||
|
}
|
||||||
|
else // closing placeholder not found in content data, store found percent symbol as is and advance to the next position
|
||||||
|
++pTemplateStart;
|
||||||
|
if(paramName.length()) {
|
||||||
|
// call callback and replace with result.
|
||||||
|
// Everything in range [pTemplateStart, pTemplateEnd] can be safely replaced with parameter value.
|
||||||
|
// Data after pTemplateEnd may need to be moved.
|
||||||
|
// The first byte of data after placeholder is located at pTemplateEnd + 1.
|
||||||
|
// It should be located at pTemplateStart + numBytesCopied (to begin right after inserted parameter value).
|
||||||
|
const String paramValue(_callback(paramName));
|
||||||
|
const char* pvstr = paramValue.c_str();
|
||||||
|
const unsigned int pvlen = paramValue.length();
|
||||||
|
const size_t numBytesCopied = std::min(pvlen, static_cast<unsigned int>(&data[originalLen - 1] - pTemplateStart + 1));
|
||||||
|
// make room for param value
|
||||||
|
// 1. move extra data to cache if parameter value is longer than placeholder AND if there is no room to store
|
||||||
|
if((pTemplateEnd + 1 < pTemplateStart + numBytesCopied) && (originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1) < len)) {
|
||||||
|
_cache.insert(_cache.begin(), &data[originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1)], &data[len]);
|
||||||
|
//2. parameter value is longer than placeholder text, push the data after placeholder which not saved into cache further to the end
|
||||||
|
memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[originalLen] - pTemplateStart - numBytesCopied);
|
||||||
|
len = originalLen; // fix issue with truncated data, not sure if it has any side effects
|
||||||
|
} else if(pTemplateEnd + 1 != pTemplateStart + numBytesCopied)
|
||||||
|
//2. Either parameter value is shorter than placeholder text OR there is enough free space in buffer to fit.
|
||||||
|
// Move the entire data after the placeholder
|
||||||
|
memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1);
|
||||||
|
// 3. replace placeholder with actual value
|
||||||
|
memcpy(pTemplateStart, pvstr, numBytesCopied);
|
||||||
|
// If result is longer than buffer, copy the remainder into cache (this could happen only if placeholder text itself did not fit entirely in buffer)
|
||||||
|
if(numBytesCopied < pvlen) {
|
||||||
|
_cache.insert(_cache.begin(), pvstr + numBytesCopied, pvstr + pvlen);
|
||||||
|
} else if(pTemplateStart + numBytesCopied < pTemplateEnd + 1) { // result is copied fully; if result is shorter than placeholder text...
|
||||||
|
// there is some free room, fill it from cache
|
||||||
|
const size_t roomFreed = pTemplateEnd + 1 - pTemplateStart - numBytesCopied;
|
||||||
|
const size_t totalFreeRoom = originalLen - len + roomFreed;
|
||||||
|
len += _readDataFromCacheOrContent(&data[len - roomFreed], totalFreeRoom) - roomFreed;
|
||||||
|
} else { // result is copied fully; it is longer than placeholder text
|
||||||
|
const size_t roomTaken = pTemplateStart + numBytesCopied - pTemplateEnd - 1;
|
||||||
|
len = std::min(len + roomTaken, originalLen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // while(pTemplateStart)
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* File Response
|
||||||
|
* */
|
||||||
|
|
||||||
|
AsyncFileResponse::~AsyncFileResponse(){
|
||||||
|
if(_content)
|
||||||
|
_content.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncFileResponse::_setContentType(const String& path){
|
||||||
|
#if HAVE_EXTERN_GET_CONTENT_TYPE_FUNCTION
|
||||||
|
extern const __FlashStringHelper *getContentType(const String &path);
|
||||||
|
_contentType = getContentType(path);
|
||||||
|
#else
|
||||||
|
if (path.endsWith(F(".html"))) _contentType = F("text/html");
|
||||||
|
else if (path.endsWith(F(".htm"))) _contentType = F("text/html");
|
||||||
|
else if (path.endsWith(F(".css"))) _contentType = F("text/css");
|
||||||
|
else if (path.endsWith(F(".json"))) _contentType = F("application/json");
|
||||||
|
else if (path.endsWith(F(".js"))) _contentType = F("application/javascript");
|
||||||
|
else if (path.endsWith(F(".png"))) _contentType = F("image/png");
|
||||||
|
else if (path.endsWith(F(".gif"))) _contentType = F("image/gif");
|
||||||
|
else if (path.endsWith(F(".jpg"))) _contentType = F("image/jpeg");
|
||||||
|
else if (path.endsWith(F(".ico"))) _contentType = F("image/x-icon");
|
||||||
|
else if (path.endsWith(F(".svg"))) _contentType = F("image/svg+xml");
|
||||||
|
else if (path.endsWith(F(".eot"))) _contentType = F("font/eot");
|
||||||
|
else if (path.endsWith(F(".woff"))) _contentType = F("font/woff");
|
||||||
|
else if (path.endsWith(F(".woff2"))) _contentType = F("font/woff2");
|
||||||
|
else if (path.endsWith(F(".ttf"))) _contentType = F("font/ttf");
|
||||||
|
else if (path.endsWith(F(".xml"))) _contentType = F("text/xml");
|
||||||
|
else if (path.endsWith(F(".pdf"))) _contentType = F("application/pdf");
|
||||||
|
else if (path.endsWith(F(".zip"))) _contentType = F("application/zip");
|
||||||
|
else if(path.endsWith(F(".gz"))) _contentType = F("application/x-gzip");
|
||||||
|
else _contentType = F("text/plain");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncFileResponse::AsyncFileResponse(FS &fs, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback): AsyncAbstractResponse(callback){
|
||||||
|
_code = 200;
|
||||||
|
_path = path;
|
||||||
|
|
||||||
|
if(!download && !fs.exists(_path) && fs.exists(_path + F(".gz"))){
|
||||||
|
_path = _path + F(".gz");
|
||||||
|
addHeader(F("Content-Encoding"), F("gzip"));
|
||||||
|
_callback = nullptr; // Unable to process zipped templates
|
||||||
|
_sendContentLength = true;
|
||||||
|
_chunked = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_content = fs.open(_path, fs::FileOpenMode::read);
|
||||||
|
_contentLength = _content.size();
|
||||||
|
|
||||||
|
if(contentType.length() == 0)
|
||||||
|
_setContentType(path);
|
||||||
|
else
|
||||||
|
_contentType = contentType;
|
||||||
|
|
||||||
|
int filenameStart = path.lastIndexOf('/') + 1;
|
||||||
|
char buf[26+path.length()-filenameStart];
|
||||||
|
char* filename = (char*)path.c_str() + filenameStart;
|
||||||
|
|
||||||
|
if(download) {
|
||||||
|
// set filename and force download
|
||||||
|
snprintf_P(buf, sizeof (buf), PSTR("attachment; filename=\"%s\""), filename);
|
||||||
|
} else {
|
||||||
|
// set filename and force rendering
|
||||||
|
snprintf_P(buf, sizeof (buf), PSTR("inline"));
|
||||||
|
}
|
||||||
|
addHeader(F("Content-Disposition"), buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncFileResponse::AsyncFileResponse(File content, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback): AsyncAbstractResponse(callback){
|
||||||
|
_code = 200;
|
||||||
|
_path = path;
|
||||||
|
|
||||||
|
if(!download && String(content.name()).endsWith(F(".gz")) && !path.endsWith(F(".gz"))){
|
||||||
|
addHeader(F("Content-Encoding"), F("gzip"));
|
||||||
|
_callback = nullptr; // Unable to process gzipped templates
|
||||||
|
_sendContentLength = true;
|
||||||
|
_chunked = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_content = content;
|
||||||
|
_contentLength = _content.size();
|
||||||
|
|
||||||
|
if(contentType.length() == 0)
|
||||||
|
_setContentType(path);
|
||||||
|
else
|
||||||
|
_contentType = contentType;
|
||||||
|
|
||||||
|
int filenameStart = path.lastIndexOf('/') + 1;
|
||||||
|
char buf[26+path.length()-filenameStart];
|
||||||
|
char* filename = (char*)path.c_str() + filenameStart;
|
||||||
|
|
||||||
|
if(download) {
|
||||||
|
snprintf_P(buf, sizeof (buf), PSTR("attachment; filename=\"%s\""), filename);
|
||||||
|
} else {
|
||||||
|
snprintf_P(buf, sizeof (buf), PSTR("inline"));
|
||||||
|
}
|
||||||
|
addHeader(F("Content-Disposition"), buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t AsyncFileResponse::_fillBuffer(uint8_t *data, size_t len){
|
||||||
|
return _content.read(data, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Stream Response
|
||||||
|
* */
|
||||||
|
|
||||||
|
AsyncStreamResponse::AsyncStreamResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback): AsyncAbstractResponse(callback) {
|
||||||
|
_code = 200;
|
||||||
|
_content = &stream;
|
||||||
|
_contentLength = len;
|
||||||
|
_contentType = contentType;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t AsyncStreamResponse::_fillBuffer(uint8_t *data, size_t len){
|
||||||
|
size_t available = _content->available();
|
||||||
|
size_t outLen = (available > len)?len:available;
|
||||||
|
size_t i;
|
||||||
|
for(i=0;i<outLen;i++)
|
||||||
|
data[i] = _content->read();
|
||||||
|
return outLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Callback Response
|
||||||
|
* */
|
||||||
|
|
||||||
|
AsyncCallbackResponse::AsyncCallbackResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback): AsyncAbstractResponse(templateCallback) {
|
||||||
|
_code = 200;
|
||||||
|
_content = callback;
|
||||||
|
_contentLength = len;
|
||||||
|
if(!len)
|
||||||
|
_sendContentLength = false;
|
||||||
|
_contentType = contentType;
|
||||||
|
_filledLength = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t AsyncCallbackResponse::_fillBuffer(uint8_t *data, size_t len){
|
||||||
|
size_t ret = _content(data, len, _filledLength);
|
||||||
|
if(ret != RESPONSE_TRY_AGAIN){
|
||||||
|
_filledLength += ret;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chunked Response
|
||||||
|
* */
|
||||||
|
|
||||||
|
AsyncChunkedResponse::AsyncChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor processorCallback): AsyncAbstractResponse(processorCallback) {
|
||||||
|
_code = 200;
|
||||||
|
_content = callback;
|
||||||
|
_contentLength = 0;
|
||||||
|
_contentType = contentType;
|
||||||
|
_sendContentLength = false;
|
||||||
|
_chunked = true;
|
||||||
|
_filledLength = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t AsyncChunkedResponse::_fillBuffer(uint8_t *data, size_t len){
|
||||||
|
size_t ret = _content(data, len, _filledLength);
|
||||||
|
if(ret != RESPONSE_TRY_AGAIN){
|
||||||
|
_filledLength += ret;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Progmem Response
|
||||||
|
* */
|
||||||
|
|
||||||
|
AsyncProgmemResponse::AsyncProgmemResponse(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback): AsyncAbstractResponse(callback) {
|
||||||
|
_code = code;
|
||||||
|
_content = content;
|
||||||
|
_contentType = contentType;
|
||||||
|
_contentLength = len;
|
||||||
|
_readLength = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t AsyncProgmemResponse::_fillBuffer(uint8_t *data, size_t len){
|
||||||
|
size_t left = _contentLength - _readLength;
|
||||||
|
if (left > len) {
|
||||||
|
memcpy_P(data, _content + _readLength, len);
|
||||||
|
_readLength += len;
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
memcpy_P(data, _content + _readLength, left);
|
||||||
|
_readLength += left;
|
||||||
|
return left;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Response Stream (You can print/write/printf to it, up to the contentLen bytes)
|
||||||
|
* */
|
||||||
|
|
||||||
|
AsyncResponseStream::AsyncResponseStream(const String& contentType, size_t bufferSize)
|
||||||
|
{
|
||||||
|
_code = 200;
|
||||||
|
_contentLength = 0;
|
||||||
|
_contentType = contentType;
|
||||||
|
_content = std::unique_ptr<cbuf>(new cbuf(bufferSize)); //std::make_unique<cbuf>(bufferSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncResponseStream::~AsyncResponseStream() = default;
|
||||||
|
|
||||||
|
size_t AsyncResponseStream::_fillBuffer(uint8_t *buf, size_t maxLen){
|
||||||
|
return _content->read((char*)buf, maxLen);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t AsyncResponseStream::write(const uint8_t *data, size_t len){
|
||||||
|
if(_started())
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if(len > _content->room()){
|
||||||
|
size_t needed = len - _content->room();
|
||||||
|
_content->resizeAdd(needed);
|
||||||
|
}
|
||||||
|
size_t written = _content->write((const char*)data, len);
|
||||||
|
_contentLength += written;
|
||||||
|
return written;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t AsyncResponseStream::write(uint8_t data){
|
||||||
|
return write(&data, 1);
|
||||||
|
}
|
||||||
198
libraries/ESPAsyncWebServer/src/WebServer.cpp
Normal file
198
libraries/ESPAsyncWebServer/src/WebServer.cpp
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
/*
|
||||||
|
Asynchronous WebServer library for Espressif MCUs
|
||||||
|
|
||||||
|
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
|
||||||
|
This file is part of the esp8266 core for Arduino environment.
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
#include "ESPAsyncWebServer.h"
|
||||||
|
#include "WebHandlerImpl.h"
|
||||||
|
|
||||||
|
bool ON_STA_FILTER(AsyncWebServerRequest *request) {
|
||||||
|
return WiFi.localIP() == request->client()->localIP();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ON_AP_FILTER(AsyncWebServerRequest *request) {
|
||||||
|
return WiFi.localIP() != request->client()->localIP();
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef HAVE_FS_FILE_OPEN_MODE
|
||||||
|
const char *fs::FileOpenMode::read = "r";
|
||||||
|
const char *fs::FileOpenMode::write = "w";
|
||||||
|
const char *fs::FileOpenMode::append = "a";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
AsyncWebServer::AsyncWebServer(uint16_t port)
|
||||||
|
: _server(port)
|
||||||
|
, _rewrites(AlternativeLinkedList<AsyncWebRewrite*>([](AsyncWebRewrite* r){ delete r; }))
|
||||||
|
, _handlers(AlternativeLinkedList<AsyncWebHandler*>([](AsyncWebHandler* h){ delete h; }))
|
||||||
|
{
|
||||||
|
_catchAllHandler = new AsyncCallbackWebHandler();
|
||||||
|
if(_catchAllHandler == NULL)
|
||||||
|
return;
|
||||||
|
_server.onClient([](void *s, AsyncClient* c){
|
||||||
|
if(c == NULL)
|
||||||
|
return;
|
||||||
|
c->setRxTimeout(3);
|
||||||
|
AsyncWebServerRequest *r = new AsyncWebServerRequest((AsyncWebServer*)s, c);
|
||||||
|
if(r == NULL){
|
||||||
|
c->close(true);
|
||||||
|
c->free();
|
||||||
|
delete c;
|
||||||
|
}
|
||||||
|
}, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncWebServer::~AsyncWebServer(){
|
||||||
|
reset();
|
||||||
|
end();
|
||||||
|
if(_catchAllHandler) delete _catchAllHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncWebRewrite& AsyncWebServer::addRewrite(AsyncWebRewrite* rewrite){
|
||||||
|
_rewrites.add(rewrite);
|
||||||
|
return *rewrite;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AsyncWebServer::removeRewrite(AsyncWebRewrite *rewrite){
|
||||||
|
return _rewrites.remove(rewrite);
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncWebRewrite& AsyncWebServer::rewrite(const char* from, const char* to){
|
||||||
|
return addRewrite(new AsyncWebRewrite(from, to));
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncWebHandler& AsyncWebServer::addHandler(AsyncWebHandler* handler){
|
||||||
|
_handlers.add(handler);
|
||||||
|
return *handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AsyncWebServer::removeHandler(AsyncWebHandler *handler){
|
||||||
|
return _handlers.remove(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncWebServer::begin(){
|
||||||
|
_server.setNoDelay(true);
|
||||||
|
_server.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncWebServer::end(){
|
||||||
|
_server.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
#if ASYNC_TCP_SSL_ENABLED
|
||||||
|
void AsyncWebServer::onSslFileRequest(AcSSlFileHandler cb, void* arg){
|
||||||
|
_server.onSslFileRequest(cb, arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncWebServer::beginSecure(const char *cert, const char *key, const char *password){
|
||||||
|
_server.beginSecure(cert, key, password);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void AsyncWebServer::_handleDisconnect(AsyncWebServerRequest *request){
|
||||||
|
delete request;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncWebServer::_rewriteRequest(AsyncWebServerRequest *request){
|
||||||
|
for(const auto& r: _rewrites){
|
||||||
|
if (r->match(request)){
|
||||||
|
request->_url = r->toUrl();
|
||||||
|
request->_addGetParams(r->params());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncWebServer::_attachHandler(AsyncWebServerRequest *request){
|
||||||
|
for(const auto& h: _handlers){
|
||||||
|
if (h->filter(request) && h->canHandle(request)){
|
||||||
|
request->setHandler(h);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
request->addInterestingHeader(F("ANY"));
|
||||||
|
request->setHandler(_catchAllHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody){
|
||||||
|
AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler();
|
||||||
|
handler->setUri(uri);
|
||||||
|
handler->setMethod(method);
|
||||||
|
handler->onRequest(onRequest);
|
||||||
|
handler->onUpload(onUpload);
|
||||||
|
handler->onBody(onBody);
|
||||||
|
addHandler(handler);
|
||||||
|
return *handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload){
|
||||||
|
AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler();
|
||||||
|
handler->setUri(uri);
|
||||||
|
handler->setMethod(method);
|
||||||
|
handler->onRequest(onRequest);
|
||||||
|
handler->onUpload(onUpload);
|
||||||
|
addHandler(handler);
|
||||||
|
return *handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest){
|
||||||
|
AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler();
|
||||||
|
handler->setUri(uri);
|
||||||
|
handler->setMethod(method);
|
||||||
|
handler->onRequest(onRequest);
|
||||||
|
addHandler(handler);
|
||||||
|
return *handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, ArRequestHandlerFunction onRequest){
|
||||||
|
AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler();
|
||||||
|
handler->setUri(uri);
|
||||||
|
handler->onRequest(onRequest);
|
||||||
|
addHandler(handler);
|
||||||
|
return *handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncStaticWebHandler& AsyncWebServer::serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_control){
|
||||||
|
AsyncStaticWebHandler* handler = new AsyncStaticWebHandler(uri, fs, path, cache_control);
|
||||||
|
addHandler(handler);
|
||||||
|
return *handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncWebServer::onNotFound(ArRequestHandlerFunction fn){
|
||||||
|
_catchAllHandler->onRequest(fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncWebServer::onFileUpload(ArUploadHandlerFunction fn){
|
||||||
|
_catchAllHandler->onUpload(fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncWebServer::onRequestBody(ArBodyHandlerFunction fn){
|
||||||
|
_catchAllHandler->onBody(fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncWebServer::reset(){
|
||||||
|
_rewrites.free();
|
||||||
|
_handlers.free();
|
||||||
|
|
||||||
|
if (_catchAllHandler != NULL){
|
||||||
|
_catchAllHandler->onRequest(NULL);
|
||||||
|
_catchAllHandler->onUpload(NULL);
|
||||||
|
_catchAllHandler->onBody(NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user