Update to version 3.8.0

This commit is contained in:
2025-08-18 11:51:22 +02:00
parent 13237c6372
commit 2617252395
70 changed files with 1541 additions and 441 deletions

246
.clang-format Normal file
View File

@@ -0,0 +1,246 @@
# Clang format version: 18.1.3
---
BasedOnStyle: LLVM
AccessModifierOffset: -2
AlignAfterOpenBracket: BlockIndent
AlignArrayOfStructures: None
AlignConsecutiveAssignments:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: true
AlignConsecutiveBitFields:
Enabled: true
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: false
AlignConsecutiveDeclarations:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: false
AlignConsecutiveMacros:
Enabled: true
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: false
AlignConsecutiveShortCaseStatements:
Enabled: true
AcrossEmptyLines: false
AcrossComments: false
AlignCaseColons: false
AlignEscapedNewlines: Left
AlignOperands: Align
AlignTrailingComments:
Kind: Always
OverEmptyLines: 0
AllowAllArgumentsOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowBreakBeforeNoexceptSpecifier: Never
AllowShortBlocksOnASingleLine: Empty
AllowShortCaseLabelsOnASingleLine: true
AllowShortCompoundRequirementOnASingleLine: true
AllowShortEnumsOnASingleLine: false
AllowShortFunctionsOnASingleLine: Empty
AllowShortIfStatementsOnASingleLine: Never
AllowShortLambdasOnASingleLine: Empty
AllowShortLoopsOnASingleLine: true
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: MultiLine
AttributeMacros:
- __capability
BinPackArguments: true
BinPackParameters: true
BitFieldColonSpacing: Both
BraceWrapping:
AfterCaseLabel: true
AfterClass: false
AfterControlStatement: Never
AfterEnum: false
AfterFunction: false
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
AfterExternBlock: false
BeforeCatch: false
BeforeElse: false
BeforeLambdaBody: false
BeforeWhile: false
IndentBraces: false
SplitEmptyFunction: false
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakAdjacentStringLiterals: true
BreakAfterAttributes: Always
BreakAfterJavaFieldAnnotations: false
BreakArrays: false
BreakBeforeBinaryOperators: NonAssignment
BreakBeforeBraces: Custom
BreakBeforeConceptDeclarations: Always
BreakBeforeInlineASMColon: OnlyMultiline
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: BeforeColon
BreakInheritanceList: BeforeColon
BreakStringLiterals: true
ColumnLimit: 160
CommentPragmas: ""
CompactNamespaces: false
ConstructorInitializerIndentWidth: 2
ContinuationIndentWidth: 2
Cpp11BracedListStyle: true
DerivePointerAlignment: false
DisableFormat: false
EmptyLineAfterAccessModifier: Never
EmptyLineBeforeAccessModifier: LogicalBlock
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IfMacros:
- KJ_IF_MAYBE
IncludeBlocks: Preserve
IncludeCategories:
- Regex: ^"(llvm|llvm-c|clang|clang-c)/
Priority: 2
SortPriority: 0
CaseSensitive: false
- Regex: ^(<|"(gtest|gmock|isl|json)/)
Priority: 3
SortPriority: 0
CaseSensitive: false
- Regex: .*
Priority: 1
SortPriority: 0
CaseSensitive: false
IncludeIsMainRegex: ""
IncludeIsMainSourceRegex: ""
IndentAccessModifiers: false
IndentCaseBlocks: false
IndentCaseLabels: true
IndentExternBlock: NoIndent
IndentGotoLabels: false
IndentPPDirectives: None
IndentRequiresClause: false
IndentWidth: 2
IndentWrappedFunctionNames: true
InsertBraces: true
InsertNewlineAtEOF: true
InsertTrailingCommas: None
IntegerLiteralSeparator:
Binary: 0
BinaryMinDigits: 0
Decimal: 0
DecimalMinDigits: 0
Hex: 0
HexMinDigits: 0
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtEOF: false
KeepEmptyLinesAtTheStartOfBlocks: true
LambdaBodyIndentation: Signature
Language: Cpp
LineEnding: LF
MacroBlockBegin: ""
MacroBlockEnd: ""
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBinPackProtocolList: Auto
ObjCBlockIndentWidth: 2
ObjCBreakBeforeNestedBlockParam: true
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PPIndentWidth: -1
PackConstructorInitializers: BinPack
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 19
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakOpenParenthesis: 0
PenaltyBreakScopeResolution: 500
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyIndentedWhitespace: 0
PenaltyReturnTypeOnItsOwnLine: 60
PointerAlignment: Right
QualifierAlignment: Leave
ReferenceAlignment: Pointer
ReflowComments: false
RemoveBracesLLVM: false
RemoveParentheses: Leave
RemoveSemicolon: false
RequiresClausePosition: OwnLine
RequiresExpressionIndentation: OuterScope
SeparateDefinitionBlocks: Leave
ShortNamespaceLines: 1
SkipMacroDefinitionBody: false
SortIncludes: Never
SortJavaStaticImport: Before
SortUsingDeclarations: LexicographicNumeric
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: false
SpaceAroundPointerQualifiers: Default
SpaceBeforeAssignmentOperators: true
SpaceBeforeCaseColon: false
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeJsonColon: false
SpaceBeforeParens: ControlStatements
SpaceBeforeParensOptions:
AfterControlStatements: true
AfterForeachMacros: true
AfterFunctionDeclarationName: false
AfterFunctionDefinitionName: false
AfterIfMacros: true
AfterOverloadedOperator: true
AfterPlacementOperator: true
AfterRequiresInClause: false
AfterRequiresInExpression: false
BeforeNonEmptyParentheses: false
SpaceBeforeRangeBasedForLoopColon: true
SpaceBeforeSquareBrackets: false
SpaceInEmptyBlock: false
SpacesBeforeTrailingComments: 2
SpacesInAngles: Never
SpacesInContainerLiterals: false
SpacesInLineCommentPrefix:
Minimum: 1
Maximum: -1
SpacesInParens: Never
SpacesInParensOptions:
InConditionalStatements: false
InCStyleCasts: false
InEmptyParentheses: false
Other: false
SpacesInSquareBrackets: false
Standard: Auto
StatementAttributeLikeMacros:
- Q_EMIT
StatementMacros:
- Q_UNUSED
- QT_REQUIRE_VERSION
TabWidth: 2
UseTab: Never
VerilogBreakBetweenInstancePorts: true
WhitespaceSensitiveMacros:
- BOOST_PP_STRINGIZE
- CF_SWIFT_NAME
- NS_SWIFT_NAME
- PP_STRINGIZE
- STRINGIZE
BracedInitializerIndentWidth: 2

8
.codespellrc Normal file
View File

@@ -0,0 +1,8 @@
[codespell]
# Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/spell-check/.codespellrc
# In the event of a false positive, add the problematic word, in all lowercase, to a comma-separated list here:
ignore-words-list = ba,licence,varius
skip = ./.git,./.licenses,__pycache__,.clang-format,.codespellrc,.editorconfig,.flake8,.prettierignore,.yamllint.yml,.gitignore
builtin = clear,informal,en-GB_to_en-US
check-filenames =
check-hidden =

60
.editorconfig Normal file
View File

@@ -0,0 +1,60 @@
# Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/general/.editorconfig
# See: https://editorconfig.org/
# The formatting style defined in this file is the official standardized style to be used in all Arduino Tooling
# projects and should not be modified.
# Note: indent style for each file type is defined even when it matches the universal config in order to make it clear
# that this type has an official style.
[*]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.{adoc,asc,asciidoc}]
indent_size = 2
indent_style = space
[*.{bash,sh}]
indent_size = 4
indent_style = space
[*.{c,cc,cp,cpp,cxx,h,hh,hpp,hxx,ii,inl,ino,ixx,pde,tpl,tpp,txx}]
indent_size = 2
indent_style = space
[*.{go,mod}]
indent_style = tab
[*.java]
indent_size = 2
indent_style = space
[*.{js,jsx,json,jsonc,json5,ts,tsx}]
indent_size = 2
indent_style = space
[*.{md,mdx,mkdn,mdown,markdown}]
indent_size = unset
indent_style = space
[*.proto]
indent_size = 2
indent_style = space
[*.py]
indent_size = 4
indent_style = space
[*.svg]
indent_size = 2
indent_style = space
[*.{yaml,yml}]
indent_size = 2
indent_style = space
[{.gitconfig,.gitmodules}]
indent_style = tab

5
.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
.DS_Store
.lh
/.pio
/.vscode
/logs

2
.gitpod.Dockerfile vendored Normal file
View File

@@ -0,0 +1,2 @@
FROM gitpod/workspace-python-3.11
USER gitpod

9
.gitpod.yml Normal file
View File

@@ -0,0 +1,9 @@
tasks:
- command: pip install --upgrade pip && pip install -U platformio && platformio run
image:
file: .gitpod.Dockerfile
vscode:
extensions:
- shardulm94.trailing-spaces

42
.pre-commit-config.yaml Normal file
View File

@@ -0,0 +1,42 @@
exclude: |
(?x)(
^\.github\/|
LICENSE$
)
default_language_version:
# force all unspecified python hooks to run python3
python: python3
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: "v5.0.0"
hooks:
# Generic checks
- id: check-case-conflict
- id: check-symlinks
- id: debug-statements
- id: destroyed-symlinks
- id: detect-private-key
- id: end-of-file-fixer
exclude: ^.*\.(bin|BIN)$
- id: mixed-line-ending
args: [--fix=lf]
- id: trailing-whitespace
args: [--markdown-linebreak-ext=md]
exclude: ^platformio\.ini$
- repo: https://github.com/codespell-project/codespell
rev: "v2.3.0"
hooks:
# Spell checking
- id: codespell
exclude: ^.*\.(svd|SVD)$
- repo: https://github.com/pre-commit/mirrors-clang-format
rev: "v18.1.3"
hooks:
# C/C++ formatting
- id: clang-format
types_or: [c, c++]
exclude: ^.*\/build_opt\.h$

View File

@@ -72,6 +72,19 @@ lib_deps =
ESP32Async/ESPAsyncWebServer
```
### LibreTiny (BK7231N/T, RTL8710B, etc.)
Version 1.9.1 or newer is required.
```ini
[env:stable]
platform = libretiny @ ^1.9.1
lib_ldf_mode = chain
lib_deps =
ESP32Async/AsyncTCP
ESP32Async/ESPAsyncWebServer
```
### Unofficial dependencies
**AsyncTCPSock**

View File

@@ -1,48 +0,0 @@
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.

View File

@@ -2,7 +2,7 @@
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
#include <DNSServer.h>
#ifdef ESP32
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
@@ -20,7 +20,7 @@ static AsyncWebServer server(80);
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif

View File

@@ -0,0 +1,210 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
//
// Shows how to trigger an async client request from a browser request and send the client response back to the browser through websocket
//
#include <Arduino.h>
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
#define WIFI_SSID "IoT"
#define WIFI_PASSWORD ""
static AsyncWebServer server(80);
static AsyncWebSocketMessageHandler wsHandler;
static AsyncWebSocket ws("/ws", wsHandler.eventHandler());
static const char *htmlContent PROGMEM = R"(
<!DOCTYPE html>
<html>
<head>
<title>WebSocket Tunnel Example</title>
</head>
<body>
<h1>WebSocket Tunnel Example</h1>
<div><input type="text" id="url" value="http://www.google.com" /></div>
<div><button onclick='fetch()'>Fetch</button></div>
<div><pre id="response"></pre></div>
<script>
var ws = new WebSocket('/ws');
ws.binaryType = "arraybuffer";
ws.onopen = function() {
console.log("WebSocket connected");
};
ws.onmessage = function(event) {
let uint8array = new Uint8Array(event.data);
let string = new TextDecoder().decode(uint8array);
console.log("WebSocket message: " + string);
document.getElementById("response").innerText += string;
};
ws.onclose = function() {
console.log("WebSocket closed");
};
ws.onerror = function(error) {
console.log("WebSocket error: " + error);
};
function fetch() {
document.getElementById("response").innerText = "";
var url = document.getElementById("url").value;
ws.send(url);
console.log("WebSocket sent: " + url);
}
</script>
</body>
</html>
)";
static const size_t htmlContentLength = strlen_P(htmlContent);
void setup() {
Serial.begin(115200);
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
}
Serial.println("Connected to WiFi!");
Serial.println(WiFi.localIP());
#endif
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "text/html", (const uint8_t *)htmlContent, htmlContentLength);
});
wsHandler.onMessage([](AsyncWebSocket *server, AsyncWebSocketClient *wsClient, const uint8_t *data, size_t len) {
String url;
String host;
String port;
String path;
url.concat((const char *)data, len);
if (!url.startsWith("http://")) {
return;
}
if (!url.endsWith("/")) {
url += "/";
}
// Parse the URL to extract the host and port
int start = url.indexOf("://") + 3;
int end = url.indexOf("/", start);
if (end == -1) {
end = url.length();
}
String hostPort = url.substring(start, end);
int colonIndex = hostPort.indexOf(":");
if (colonIndex != -1) {
host = hostPort.substring(0, colonIndex);
port = hostPort.substring(colonIndex + 1);
} else {
host = hostPort;
port = "80"; // Default HTTP port
}
path = url.substring(end);
Serial.printf("Host: %s\n", host.c_str());
Serial.printf("Port: %s\n", port.c_str());
Serial.printf("Path: %s\n", path.c_str());
// Ensure client does not get deleted while the websocket holds a reference to it
std::shared_ptr<AsyncClient> *safeAsyncClient = new std::shared_ptr<AsyncClient>(std::make_shared<AsyncClient>());
AsyncClient *asyncClient = safeAsyncClient->get();
asyncClient->onDisconnect([safeAsyncClient](void *arg, AsyncClient *client) {
Serial.printf("Tunnel disconnected!\n");
delete safeAsyncClient;
});
// register a callback when an error occurs
// note: onDisconnect also called on error
asyncClient->onError([](void *arg, AsyncClient *client, int8_t error) {
Serial.printf("Tunnel error: %s\n", client->errorToString(error));
});
// register a callback when data arrives, to accumulate it
asyncClient->onPacket(
[safeAsyncClient](void *arg, AsyncClient *, struct pbuf *pb) {
std::shared_ptr<AsyncClient> safeAsyncClientRef = *safeAsyncClient; // add a reference
AsyncWebSocketClient *wsClient = (AsyncWebSocketClient *)arg;
Serial.printf("Tunnel received %u bytes\n", pb->len);
AsyncWebSocketSharedBuffer wsBuffer =
AsyncWebSocketSharedBuffer(new std::vector<uint8_t>((uint8_t *)pb->payload, (uint8_t *)pb->payload + pb->len), [=](std::vector<uint8_t> *bufptr) {
delete bufptr;
Serial.printf("ACK %u bytes\n", pb->len);
safeAsyncClientRef->ackPacket(pb);
});
Serial.printf("Tunnel sending %u bytes\n", wsBuffer->size());
Serial.printf("%.*s\n", (int)wsBuffer->size(), wsBuffer->data());
wsClient->binary(std::move(wsBuffer));
},
wsClient
);
asyncClient->onConnect([=](void *arg, AsyncClient *client) {
Serial.printf("Tunnel connected!\n");
client->write("GET ");
client->write(path.c_str());
client->write(" HTTP/1.1\r\n");
client->write("Host: ");
client->write(host.c_str());
client->write(":");
client->write(port.c_str());
client->write("\r\n");
client->write("User-Agent: ESP32\r\n");
client->write("Accept: */*\r\n");
client->write("Connection: close\r\n");
client->write("\r\n");
});
Serial.printf("Fetching: http://%s:%s%s\n", host.c_str(), port.c_str(), path.c_str());
if (!asyncClient->connect(host.c_str(), port.toInt())) {
Serial.printf("Failed to open tunnel!\n");
delete safeAsyncClient;
}
});
wsHandler.onConnect([](AsyncWebSocket *server, AsyncWebSocketClient *client) {
Serial.printf("Client %" PRIu32 " connected\n", client->id());
client->binary("WebSocket connected!");
});
wsHandler.onDisconnect([](AsyncWebSocket *server, uint32_t clientId) {
Serial.printf("Client %" PRIu32 " disconnected\n", clientId);
});
server.addHandler(&ws);
server.begin();
Serial.println("Server started!");
}
static uint32_t lastHeap = 0;
void loop() {
ws.cleanupClients(2);
#ifdef ESP32
uint32_t now = millis();
if (now - lastHeap >= 2000) {
Serial.printf("Uptime: %3lu s, Free heap: %" PRIu32 "\n", millis() / 1000, ESP.getFreeHeap());
lastHeap = now;
}
#endif
delay(500);
}

View File

@@ -6,7 +6,7 @@
//
#include <Arduino.h>
#ifdef ESP32
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
@@ -52,7 +52,7 @@ static AsyncAuthorizationMiddleware authz([](AsyncWebServerRequest *request) {
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif

View File

@@ -6,7 +6,7 @@
//
#include <Arduino.h>
#ifdef ESP32
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
@@ -25,7 +25,7 @@ static AsyncCorsMiddleware cors;
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif

View File

@@ -2,7 +2,7 @@
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
#include <DNSServer.h>
#ifdef ESP32
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
@@ -28,7 +28,7 @@ public:
response->print("<!DOCTYPE html><html><head><title>Captive Portal</title></head><body>");
response->print("<p>This is our captive portal front page.</p>");
response->printf("<p>You were trying to reach: http://%s%s</p>", request->host().c_str(), request->url().c_str());
#ifndef CONFIG_IDF_TARGET_ESP32H2
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
response->printf("<p>Try opening <a href='http://%s'>this link</a> instead</p>", WiFi.softAPIP().toString().c_str());
#endif
response->print("</body></html>");
@@ -41,7 +41,7 @@ void setup() {
Serial.println();
Serial.println("Configuring access point...");
#ifndef CONFIG_IDF_TARGET_ESP32H2
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
if (!WiFi.softAP("esp-captive")) {
Serial.println("Soft AP creation failed.");
while (1);

View File

@@ -6,7 +6,7 @@
//
#include <Arduino.h>
#ifdef ESP32
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
@@ -86,7 +86,7 @@ static const size_t htmlContentLength = strlen_P(htmlContent);
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif

View File

@@ -6,7 +6,7 @@
//
#include <Arduino.h>
#ifdef ESP32
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
@@ -86,7 +86,7 @@ static const size_t htmlContentLength = strlen_P(htmlContent);
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif

View File

@@ -6,7 +6,7 @@
//
#include <Arduino.h>
#ifdef ESP32
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
@@ -96,7 +96,7 @@ static int key = -1;
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
@@ -131,9 +131,17 @@ void setup() {
"/api", HTTP_POST,
[](AsyncWebServerRequest *request) {
// request parsing has finished
String *data = (String *)request->_tempObject;
if (!data) {
request->send(400);
return;
}
// no data ?
if (!((String *)request->_tempObject)->length()) {
if (!data->length()) {
delete data;
request->_tempObject = nullptr;
request->send(400);
return;
}
@@ -141,11 +149,16 @@ void setup() {
JsonDocument doc;
// deserialize and check for errors
if (deserializeJson(doc, *(String *)request->_tempObject)) {
if (deserializeJson(doc, *data)) {
delete data;
request->_tempObject = nullptr;
request->send(400);
return;
}
delete data;
request->_tempObject = nullptr;
// start UART com: UART will send the data to the Serial console and wait for the key press
triggerUART = doc["input"].as<const char *>();
key = -1;

View File

@@ -6,7 +6,7 @@
//
#include <Arduino.h>
#ifdef ESP32
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
@@ -24,7 +24,7 @@ static AsyncWebServer server(80);
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif

View File

@@ -6,7 +6,7 @@
//
#include <DNSServer.h>
#ifdef ESP32
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
@@ -32,7 +32,7 @@ public:
response->print("<!DOCTYPE html><html><head><title>Captive Portal</title></head><body>");
response->print("<p>This is out captive portal front page.</p>");
response->printf("<p>You were trying to reach: http://%s%s</p>", request->host().c_str(), request->url().c_str());
#ifndef CONFIG_IDF_TARGET_ESP32H2
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
response->printf("<p>Try opening <a href='http://%s'>this link</a> instead</p>", WiFi.softAPIP().toString().c_str());
#endif
response->print("</body></html>");
@@ -51,17 +51,17 @@ void setup() {
"/", HTTP_GET,
[](AsyncWebServerRequest *request) {
Serial.println("Captive portal request...");
#ifndef CONFIG_IDF_TARGET_ESP32H2
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
Serial.println("WiFi.localIP(): " + WiFi.localIP().toString());
#endif
Serial.println("request->client()->localIP(): " + request->client()->localIP().toString());
#if ESP_IDF_VERSION_MAJOR >= 5
#ifndef CONFIG_IDF_TARGET_ESP32H2
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
Serial.println("WiFi.type(): " + String((int)WiFi.localIP().type()));
#endif
Serial.println("request->client()->type(): " + String((int)request->client()->localIP().type()));
#endif
#ifndef CONFIG_IDF_TARGET_ESP32H2
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
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());
@@ -77,17 +77,17 @@ void setup() {
"/", HTTP_GET,
[](AsyncWebServerRequest *request) {
Serial.println("Website request...");
#ifndef CONFIG_IDF_TARGET_ESP32H2
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
Serial.println("WiFi.localIP(): " + WiFi.localIP().toString());
#endif
Serial.println("request->client()->localIP(): " + request->client()->localIP().toString());
#if ESP_IDF_VERSION_MAJOR >= 5
#ifndef CONFIG_IDF_TARGET_ESP32H2
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
Serial.println("WiFi.type(): " + String((int)WiFi.localIP().type()));
#endif
Serial.println("request->client()->type(): " + String((int)request->client()->localIP().type()));
#endif
#ifndef CONFIG_IDF_TARGET_ESP32H2
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
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());
@@ -113,7 +113,7 @@ void setup() {
// dnsServer.stop();
// WiFi.softAPdisconnect();
#ifndef CONFIG_IDF_TARGET_ESP32H2
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
WiFi.persistent(false);
WiFi.begin("IoT");
while (WiFi.status() != WL_CONNECTED) {

View File

@@ -6,7 +6,7 @@
//
#include <Arduino.h>
#ifdef ESP32
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
@@ -86,7 +86,7 @@ static const size_t htmlContentLength = strlen_P(htmlContent);
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif

View File

@@ -6,7 +6,7 @@
//
#include <Arduino.h>
#ifdef ESP32
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
@@ -33,7 +33,7 @@ AsyncHeaderFreeMiddleware headerFree;
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
@@ -79,6 +79,29 @@ void setup() {
)
.addMiddleware(&headerFree);
// curl -v http://192.168.4.1/
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", "Hello, world!");
response->addHeader(AsyncWebHeader::parse("X-Test-1: value1"));
response->addHeader(AsyncWebHeader::parse("X-Test-2:value2"));
response->addHeader(AsyncWebHeader::parse("X-Test-3:"));
response->addHeader(AsyncWebHeader::parse("X-Test-4: "));
response->addHeader(AsyncWebHeader::parse(""));
response->addHeader(AsyncWebHeader::parse(":"));
request->send(response);
/**
< HTTP/1.1 200 OK
< connection: close
< X-Test-1: value1
< X-Test-2: value2
< X-Test-3:
< X-Test-4:
< accept-ranges: none
< content-length: 13
< content-type: text/plain
*/
});
server.begin();
}

View File

@@ -6,7 +6,7 @@
//
#include <Arduino.h>
#ifdef ESP32
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
@@ -24,7 +24,7 @@ static AsyncWebServer server(80);
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif

View File

@@ -6,7 +6,7 @@
//
#include <Arduino.h>
#ifdef ESP32
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
@@ -34,7 +34,7 @@ static AsyncCallbackJsonWebHandler *handler = new AsyncCallbackJsonWebHandler("/
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
@@ -63,14 +63,27 @@ void setup() {
JsonObject root = doc.to<JsonObject>();
root["foo"] = "bar";
serializeJson(root, *response);
Serial.println();
request->send(response);
});
// curl -v -X POST -H 'Content-Type: application/json' -d '{"name":"You"}' http://192.168.4.1/json2
// curl -v -X PUT -H 'Content-Type: application/json' -d '{"name":"You"}' http://192.168.4.1/json2
//
// edge cases:
//
// curl -v -X POST -H "Content-Type: application/json" -d "1234" -H "Content-Length: 5" http://192.168.4.1/json2 => rx timeout
// curl -v -X POST -H "Content-Type: application/json" -d "1234" -H "Content-Length: 2" http://192.168.4.1/json2 => 12
// curl -v -X POST -H "Content-Type: application/json" -d "1234" -H "Content-Length: 4" http://192.168.4.1/json2 => 1234
// curl -v -X POST -H "Content-Type: application/json" -d "1234" -H "Content-Length: 10" http://192.168.4.1/json2 => rx timeout
// curl -v -X POST -H "Content-Type: application/json" -d "12345678" -H "Content-Length: 8" http://192.168.4.1/json2 => 12345678
// curl -v -X POST -H "Content-Type: application/json" -d "123456789" -H "Content-Length: 8" http://192.168.4.1/json2 => 12345678
// curl -v -X POST -H "Content-Type: application/json" -d "123456789" -H "Content-Length: 9" http://192.168.4.1/json2 => 413: Content length exceeds maximum allowed
handler->setMaxContentLength(8);
handler->setMethod(HTTP_POST | HTTP_PUT);
handler->onRequest([](AsyncWebServerRequest *request, JsonVariant &json) {
serializeJson(json, Serial);
Serial.println();
AsyncJsonResponse *response = new AsyncJsonResponse();
JsonObject root = response->getRoot().to<JsonObject>();
root["hello"] = json.as<JsonObject>()["name"];

View File

@@ -6,7 +6,7 @@
//
#include <Arduino.h>
#ifdef ESP32
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
@@ -25,7 +25,7 @@ static AsyncLoggingMiddleware requestLogger;
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif

View File

@@ -6,7 +6,7 @@
//
#include <Arduino.h>
#ifdef ESP32
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
@@ -34,7 +34,7 @@ static AsyncCallbackMessagePackWebHandler *handler = new AsyncCallbackMessagePac
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif

View File

@@ -6,7 +6,7 @@
//
#include <Arduino.h>
#ifdef ESP32
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
@@ -34,7 +34,7 @@ public:
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif

View File

@@ -6,7 +6,7 @@
//
#include <Arduino.h>
#ifdef ESP32
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
@@ -74,7 +74,7 @@ static const size_t htmlContentLength = strlen_P(htmlContent);
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif

View File

@@ -7,7 +7,7 @@
//
#include <Arduino.h>
#ifdef ESP32
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
@@ -34,7 +34,7 @@ static AsyncWebServer server(80);
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif

View File

@@ -6,7 +6,7 @@
//
#include <Arduino.h>
#ifdef ESP32
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
@@ -91,7 +91,7 @@ static volatile size_t requests = 0;
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif

View File

@@ -6,7 +6,7 @@
//
#include <Arduino.h>
#ifdef ESP32
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
@@ -25,7 +25,7 @@ static AsyncRateLimitMiddleware rateLimit;
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif

View File

@@ -6,7 +6,7 @@
//
#include <Arduino.h>
#ifdef ESP32
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
@@ -24,7 +24,7 @@ static AsyncWebServer server(80);
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif

View File

@@ -6,7 +6,7 @@
//
#include <Arduino.h>
#ifdef ESP32
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
@@ -34,7 +34,7 @@ static AsyncWebServerRequestPtr gpioRequest;
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif

View File

@@ -6,7 +6,7 @@
//
#include <Arduino.h>
#ifdef ESP32
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
@@ -94,7 +94,7 @@ static bool processLongRunningOperation(LongRunningOperation *op) {
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif

View File

@@ -6,7 +6,7 @@
//
#include <Arduino.h>
#ifdef ESP32
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
@@ -24,7 +24,7 @@ static AsyncWebServer server(80);
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif

View File

@@ -6,7 +6,7 @@
//
#include <Arduino.h>
#ifdef ESP32
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
@@ -24,7 +24,7 @@ static AsyncWebServer server(80);
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif

View File

@@ -6,7 +6,7 @@
//
#include <Arduino.h>
#ifdef ESP32
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
@@ -58,7 +58,7 @@ static AsyncEventSource events("/events");
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif

View File

@@ -0,0 +1,141 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
//
// SSE example
//
#include <Arduino.h>
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
static const char *htmlContent PROGMEM = R"(
<!DOCTYPE html>
<html>
<head>
<title>Server-Sent Events</title>
<script>
if (!!window.EventSource) {
var source = new EventSource('/events');
source.onopen = function(e) {
console.log("Events Connected");
};
source.onerror = function(e) {
if (e.target.readyState != EventSource.OPEN) {
console.log("Events Disconnected");
}
// Uncomment below to prevent the client from proactively establishing a new connection.
// source.close();
};
source.onmessage = function(e) {
console.log("Message: " + e.data);
};
source.addEventListener('heartbeat', function(e) {
console.log("Heartbeat", e.data);
}, false);
}
</script>
</head>
<body>
<h1>Open your browser console!</h1>
</body>
</html>
)";
static const size_t htmlContentLength = strlen_P(htmlContent);
static AsyncWebServer server(80);
static AsyncEventSource events("/events");
static volatile size_t connectionCount = 0;
static volatile uint32_t timestampConnected = 0;
static constexpr uint32_t timeoutClose = 15000;
void setup() {
Serial.begin(115200);
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
// curl -v http://192.168.4.1/
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
// need to cast to uint8_t*
// if you do not, the const char* will be copied in a temporary String buffer
request->send(200, "text/html", (uint8_t *)htmlContent, htmlContentLength);
});
events.onConnect([](AsyncEventSourceClient *client) {
/**
* @brief: Purpose for a test case: count() function
* Task watchdog shall be triggered due to a self-deadlock by mutex handling of the AsyncEventSource.
*
* E (61642) task_wdt: Task watchdog got triggered. The following tasks did not reset the watchdog in time:
* E (61642) task_wdt: - async_tcp (CPU 0/1)
*
* Resolve: using recursive_mutex insteads of mutex.
*/
connectionCount = events.count();
timestampConnected = millis();
Serial.printf("SSE Client connected! ID: %" PRIu32 "\n", client->lastId());
client->send("hello!", NULL, millis(), 1000);
Serial.printf("Number of connected clients: %u\n", connectionCount);
});
events.onDisconnect([](AsyncEventSourceClient *client) {
connectionCount = events.count();
Serial.printf("SSE Client disconnected! ID: %" PRIu32 "\n", client->lastId());
Serial.printf("Number of connected clients: %u\n", connectionCount);
});
server.addHandler(&events);
server.begin();
}
static constexpr uint32_t deltaSSE = 3000;
static uint32_t lastSSE = 0;
static uint32_t lastHeap = 0;
void loop() {
uint32_t now = millis();
if (connectionCount > 0) {
if (now - lastSSE >= deltaSSE) {
events.send(String("ping-") + now, "heartbeat", now);
lastSSE = millis();
}
/**
* @brief: Purpose for a test case: close() function
* Task watchdog shall be triggered due to a self-deadlock by mutex handling of the AsyncEventSource.
*
* E (61642) task_wdt: Task watchdog got triggered. The following tasks did not reset the watchdog in time:
* E (61642) task_wdt: - async_tcp (CPU 0/1)
*
* Resolve: using recursive_mutex insteads of mutex.
*/
if (now - timestampConnected >= timeoutClose) {
Serial.printf("SSE Clients close\n");
events.close();
}
}
#ifdef ESP32
if (now - lastHeap >= 2000) {
Serial.printf("Free heap: %" PRIu32 "\n", ESP.getFreeHeap());
lastHeap = now;
}
#endif
}

View File

@@ -6,7 +6,7 @@
//
#include <Arduino.h>
#ifdef ESP32
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
@@ -25,7 +25,7 @@ static AsyncWebServer server2(80);
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif

View File

@@ -6,7 +6,7 @@
//
#include <Arduino.h>
#ifdef ESP32
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
@@ -27,7 +27,7 @@ static AsyncLoggingMiddleware logging;
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif

View File

@@ -7,7 +7,7 @@
//
#include <Arduino.h>
#ifdef ESP32
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
@@ -89,7 +89,7 @@ static size_t charactersIndex = 0;
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif

View File

@@ -6,7 +6,7 @@
//
#include <Arduino.h>
#ifdef ESP32
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
@@ -84,10 +84,34 @@ static const char *htmlContent PROGMEM = R"(
static const size_t htmlContentLength = strlen_P(htmlContent);
// sample_html_gz.h
static const uint8_t index2_html_gz[] = {
0x1f, 0x8b, 0x08, 0x08, 0x13, 0x45, 0x92, 0x68, 0x00, 0x03, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x32, 0x2e, 0x68, 0x74, 0x6d, 0x6c, 0x00, 0xed, 0x98, 0xcb,
0x6e, 0xdc, 0x30, 0x0c, 0x45, 0xf7, 0xf3, 0x15, 0xcc, 0xde, 0xb0, 0x91, 0xbd, 0xe1, 0x4d, 0x1f, 0x48, 0x81, 0xbc, 0x8a, 0x26, 0x2d, 0xba, 0xe4, 0x48,
0x8c, 0x87, 0x81, 0x1e, 0x0e, 0x25, 0x19, 0xc8, 0xdf, 0x97, 0xb2, 0x67, 0x82, 0x6c, 0xfa, 0x07, 0x32, 0x0c, 0x58, 0xa6, 0xa8, 0xcb, 0x4b, 0xe9, 0xac,
0x34, 0x5e, 0x7d, 0x7d, 0xf8, 0xf2, 0xf4, 0xf7, 0xf1, 0x1b, 0x9c, 0xb2, 0x77, 0xd3, 0x61, 0xbc, 0x7c, 0x08, 0xed, 0x74, 0x00, 0x7d, 0xc6, 0xcc, 0xd9,
0xd1, 0xf4, 0x0b, 0xfd, 0xe2, 0x08, 0x6e, 0x9e, 0xee, 0x6e, 0xc7, 0x61, 0x0f, 0x1d, 0xc6, 0x61, 0x4f, 0x1b, 0x8f, 0xd1, 0xbe, 0x9f, 0xb3, 0x4f, 0xd7,
0xd3, 0x0d, 0x39, 0x17, 0x3b, 0xf8, 0x13, 0xc5, 0xd9, 0x2b, 0xcd, 0xb9, 0x3e, 0x4f, 0x2d, 0xd3, 0x6d, 0x14, 0xf2, 0xc0, 0x4b, 0x2a, 0x1e, 0x6c, 0x74,
0x51, 0x20, 0x71, 0x06, 0xf4, 0x94, 0x3b, 0x30, 0x31, 0x24, 0x32, 0x99, 0x72, 0x11, 0x40, 0xcb, 0x0b, 0x27, 0xc3, 0x61, 0x06, 0x72, 0x9c, 0x7b, 0x78,
0x94, 0xc8, 0x01, 0xa8, 0x70, 0xf2, 0xd1, 0x76, 0xb0, 0x14, 0x29, 0x09, 0xf0, 0x12, 0xd8, 0xe4, 0xe5, 0x14, 0x83, 0x29, 0xa9, 0x83, 0x22, 0x01, 0xcf,
0x35, 0x4c, 0x91, 0xa4, 0x89, 0x1e, 0x53, 0xc2, 0x4e, 0xb3, 0xc1, 0xb2, 0xc9, 0x1a, 0xcf, 0xea, 0x50, 0xe3, 0xaf, 0x25, 0xe5, 0x08, 0x68, 0xf6, 0x41,
0x0f, 0x3f, 0x55, 0xee, 0xad, 0x10, 0x14, 0xe7, 0xd0, 0x9b, 0x28, 0x0b, 0xc9, 0x26, 0x8d, 0x62, 0x0a, 0x04, 0x32, 0x90, 0xa3, 0xe8, 0xfb, 0x79, 0xbe,
0x83, 0x95, 0x1c, 0xbc, 0x90, 0x78, 0x0a, 0x55, 0x79, 0x97, 0xfc, 0xf8, 0xef, 0xe1, 0x37, 0xaf, 0xe8, 0xb5, 0x56, 0x22, 0x5b, 0x53, 0xb5, 0xdd, 0x92,
0xb7, 0xa6, 0x76, 0x65, 0x63, 0x8a, 0x4f, 0x18, 0x6a, 0xf7, 0x73, 0xad, 0xbc, 0x4f, 0x07, 0xd6, 0x95, 0xcf, 0xb9, 0x3a, 0xde, 0x05, 0x75, 0xe0, 0x50,
0xbb, 0x83, 0x15, 0x85, 0xf5, 0x33, 0x0b, 0xae, 0x6c, 0xb1, 0x26, 0xe3, 0xb9, 0x9b, 0x1e, 0xee, 0xab, 0x2f, 0x78, 0x41, 0xc3, 0x8e, 0x13, 0xf7, 0x5b,
0x81, 0x1f, 0x21, 0xd3, 0x4c, 0xba, 0xa3, 0xc5, 0x54, 0xe7, 0x9f, 0x37, 0xb9, 0xb8, 0x2c, 0x6c, 0x98, 0x74, 0xe5, 0xf7, 0x92, 0x0c, 0xa9, 0xeb, 0x32,
0x33, 0xea, 0x51, 0x78, 0xfe, 0x38, 0x17, 0x38, 0xf2, 0x91, 0x82, 0xd5, 0xce, 0x56, 0x5e, 0x49, 0x44, 0xb7, 0x31, 0x8a, 0x61, 0x70, 0x14, 0x37, 0x7d,
0x8b, 0x0b, 0x1f, 0xd5, 0x50, 0xed, 0xa8, 0x03, 0xb6, 0x17, 0x83, 0x49, 0xcf, 0xd9, 0x16, 0xae, 0x91, 0xcd, 0x78, 0x3f, 0x0e, 0x4b, 0xc3, 0xa0, 0x61,
0xd0, 0x30, 0x68, 0x18, 0x34, 0x0c, 0x1a, 0x06, 0x0d, 0x83, 0x86, 0x41, 0xc3, 0xa0, 0x61, 0xd0, 0x30, 0x68, 0x18, 0x34, 0x0c, 0x1a, 0x06, 0xff, 0xc1,
0x60, 0x1c, 0xf6, 0xab, 0x85, 0x71, 0xd8, 0xee, 0x25, 0xfe, 0x01, 0xa0, 0xec, 0x78, 0xfe, 0xae, 0x10, 0x00, 0x00
};
static const size_t index2_html_gz_len = sizeof(index2_html_gz);
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
@@ -105,6 +129,13 @@ void setup() {
f.close();
}
{
File f = LittleFS.open("/index2.html.gz", "w");
assert(f);
f.write(index2_html_gz, index2_html_gz_len);
f.close();
}
LittleFS.mkdir("/files");
{
@@ -129,6 +160,9 @@ void setup() {
// curl -v http://192.168.4.1/index.html
server.serveStatic("/index.html", LittleFS, "/index.html");
// curl -v http://192.168.4.1/index2.html | gunzip -c
server.serveStatic("/index2.html", LittleFS, "/index2.html");
// Example to serve a directory content
// curl -v http://192.168.4.1/base/ => serves a.txt
// curl -v http://192.168.4.1/base/a.txt => serves a.txt

View File

@@ -6,7 +6,7 @@
//
#include <Arduino.h>
#ifdef ESP32
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
@@ -36,7 +36,7 @@ static const size_t htmlContentLength = strlen_P(htmlContent);
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif

View File

@@ -6,7 +6,7 @@
//
#include <Arduino.h>
#ifdef ESP32
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
@@ -31,7 +31,7 @@ void setup() {
LittleFS.begin();
}
#ifndef CONFIG_IDF_TARGET_ESP32H2
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif

View File

@@ -6,7 +6,7 @@
//
#include <Arduino.h>
#ifdef ESP32
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
@@ -25,7 +25,7 @@ static AsyncWebSocket ws("/ws");
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
@@ -102,8 +102,10 @@ void loop() {
}
if (now - lastHeap >= 2000) {
// cleanup disconnected clients or too many clients
ws.cleanupClients();
Serial.printf("Connected clients: %u / %u total\n", ws.count(), ws.getClients().size());
// this can be called to also set a soft limit on the number of connected clients
ws.cleanupClients(2); // no more than 2 clients
#ifdef ESP32
Serial.printf("Free heap: %" PRIu32 "\n", ESP.getFreeHeap());

View File

@@ -6,7 +6,7 @@
//
#include <Arduino.h>
#ifdef ESP32
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
@@ -71,7 +71,7 @@ static const size_t htmlContentLength = strlen_P(htmlContent);
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif

View File

@@ -25,11 +25,14 @@ files:
- "platformio.ini"
- "pre-commit.requirements.txt"
dependencies:
espressif/arduino-esp32:
version: "^3.1.1"
require: public
esp32async/asynctcp:
version: "^3.3.8"
version: "^3.4.7"
require: public
bblanchon/arduinojson:
version: "^7.3.1"
version: "^7.4.2"
require: public
examples:
- path: ./idf_component_examples/catchall

View File

@@ -78,7 +78,7 @@ static const size_t htmlContentLength = strlen_P(htmlContent);
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif

View File

@@ -50,7 +50,7 @@ static AsyncEventSource events("/events");
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif

View File

@@ -17,7 +17,7 @@ static AsyncWebSocket ws("/ws");
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif

View File

@@ -1,6 +1,6 @@
{
"name": "ESPAsyncWebServer",
"version": "3.7.5",
"version": "3.8.0",
"description": "Asynchronous HTTP and WebSocket Server Library for ESP32, ESP8266 and RP2040. Supports: WebSocket, SSE, Authentication, Arduino Json 7, File Upload, Static File serving, URL Rewrite, URL Redirect, etc.",
"keywords": "http,async,websocket,webserver",
"homepage": "https://github.com/ESP32Async/ESPAsyncWebServer",
@@ -18,14 +18,18 @@
"platforms": [
"espressif32",
"espressif8266",
"raspberrypi"
"raspberrypi",
"libretiny"
],
"dependencies": [
{
"owner": "ESP32Async",
"name": "AsyncTCP",
"version": "^3.3.8",
"platforms": "espressif32"
"version": "^3.4.7",
"platforms": [
"espressif32",
"libretiny"
]
},
{
"owner": "ESP32Async",

View File

@@ -1,6 +1,6 @@
name=ESP Async WebServer
includes=ESPAsyncWebServer.h
version=3.7.5
version=3.8.0
author=ESP32Async
maintainer=ESP32Async
sentence=Asynchronous HTTP and WebSocket Server Library for ESP32, ESP8266 and RP2040

View File

@@ -1,6 +1,6 @@
[env]
framework = arduino
platform = https://github.com/pioarduino/platform-espressif32/releases/download/54.03.20/platform-espressif32.zip
platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.30-2/platform-espressif32.zip
build_flags =
-Og
-Wall -Wextra
@@ -17,7 +17,7 @@ monitor_filters = esp32_exception_decoder, log2file
lib_compat_mode = strict
lib_ldf_mode = chain
lib_deps =
ESP32Async/AsyncTCP @ 3.3.8
ESP32Async/AsyncTCP @ 3.4.7
ESP32Async/ESpAsyncWebServer @ 3.7.0
custom_sdkconfig = CONFIG_LWIP_MAX_ACTIVE_TCP=32

View File

@@ -2,6 +2,7 @@
default_envs = arduino-2, arduino-3, esp8266, raspberrypi
lib_dir = .
; src_dir = examples/AsyncResponseStream
; src_dir = examples/AsyncTunnel
; src_dir = examples/Auth
; src_dir = examples/CaptivePortal
; src_dir = examples/CatchAllHandler
@@ -37,7 +38,7 @@ src_dir = examples/PerfTests
[env]
framework = arduino
platform = https://github.com/pioarduino/platform-espressif32/releases/download/54.03.20/platform-espressif32.zip
platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.30-2/platform-espressif32.zip
board = esp32dev
build_flags =
-Og
@@ -58,24 +59,26 @@ monitor_filters = esp32_exception_decoder, log2file
lib_compat_mode = strict
lib_ldf_mode = chain
lib_deps =
bblanchon/ArduinoJson @ 7.3.1
ESP32Async/AsyncTCP @ 3.3.8
bblanchon/ArduinoJson @ 7.4.2
ESP32Async/AsyncTCP @ 3.4.7
board_build.partitions = partitions-4MB.csv
board_build.filesystem = littlefs
[env:arduino-2]
platform = espressif32@6.10.0
platform = espressif32@6.12.0
[env:arduino-3]
; board = esp32-p4
; board = esp32-h2-devkitm-1
[env:arduino-3-latest]
[env:arduino-rc]
platform = https://github.com/pioarduino/platform-espressif32/releases/download/54.03.20-rc2/platform-espressif32.zip
[env:arduino-3-no-json]
lib_deps =
ESP32Async/AsyncTCP @ 3.3.8
ESP32Async/AsyncTCP @ 3.4.7
[env:arduino-3-latest-asynctcp]
[env:arduino-rc-asynctcp]
lib_deps =
https://github.com/ESP32Async/AsyncTCP
@@ -93,7 +96,7 @@ platform = espressif8266
; board = huzzah
board = d1_mini
lib_deps =
bblanchon/ArduinoJson @ 7.3.1
bblanchon/ArduinoJson @ 7.4.2
ESP32Async/ESPAsyncTCP @ 2.0.0
[env:raspberrypi]
@@ -108,25 +111,36 @@ lib_ignore =
build_flags = ${env.build_flags}
-Wno-missing-field-initializers
[env:libretiny]
platform = libretiny @ ^1.9.1
board = generic-bk7231n-qfn32-tuya
; board = generic-rtl8710bn-2mb-788k
lib_compat_mode = off
lib_deps =
ESP32Async/AsyncTCP @ 3.4.3
; use FreeRTOS v9.0.0 for RTL8710BN
; (BK7231 already uses it)
custom_versions.freertos = 9.0.0
; CI
[env:ci-arduino-2]
platform = espressif32@6.10.0
platform = espressif32@6.12.0
board = ${sysenv.PIO_BOARD}
[env:ci-arduino-3]
board = ${sysenv.PIO_BOARD}
[env:ci-arduino-3-latest]
[env:ci-arduino-rc]
platform = https://github.com/pioarduino/platform-espressif32/releases/download/54.03.20-rc2/platform-espressif32.zip
board = ${sysenv.PIO_BOARD}
[env:ci-arduino-3-no-json]
board = ${sysenv.PIO_BOARD}
lib_deps =
ESP32Async/AsyncTCP @ 3.3.8
ESP32Async/AsyncTCP @ 3.4.7
[env:ci-arduino-3-latest-asynctcp]
[env:ci-arduino-rc-asynctcp]
lib_deps =
https://github.com/ESP32Async/AsyncTCP
@@ -139,7 +153,7 @@ build_flags = ${env.build_flags}
platform = espressif8266
board = ${sysenv.PIO_BOARD}
lib_deps =
bblanchon/ArduinoJson @ 7.3.1
bblanchon/ArduinoJson @ 7.4.2
ESP32Async/ESPAsyncTCP @ 2.0.0
[env:ci-raspberrypi]
@@ -153,3 +167,13 @@ lib_ignore =
lwIP_ESPHost
build_flags = ${env.build_flags}
-Wno-missing-field-initializers
[env:ci-libretiny]
platform = libretiny @ ^1.9.1
board = ${sysenv.PIO_BOARD}
lib_compat_mode = off
lib_deps =
; add DNS server library for LibreTiny
DNSServer
ESP32Async/AsyncTCP @ 3.4.3
custom_versions.freertos = 9.0.0

View File

@@ -193,7 +193,7 @@ AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest *request, A
AsyncEventSourceClient::~AsyncEventSourceClient() {
#ifdef ESP32
std::lock_guard<std::mutex> lock(_lockmq);
std::lock_guard<std::recursive_mutex> lock(_lockmq);
#endif
_messageQueue.clear();
close();
@@ -211,7 +211,7 @@ bool AsyncEventSourceClient::_queueMessage(const char *message, size_t len) {
#ifdef ESP32
// length() is not thread-safe, thus acquiring the lock before this call..
std::lock_guard<std::mutex> lock(_lockmq);
std::lock_guard<std::recursive_mutex> lock(_lockmq);
#endif
_messageQueue.emplace_back(message, len);
@@ -241,7 +241,7 @@ bool AsyncEventSourceClient::_queueMessage(AsyncEvent_SharedData_t &&msg) {
#ifdef ESP32
// length() is not thread-safe, thus acquiring the lock before this call..
std::lock_guard<std::mutex> lock(_lockmq);
std::lock_guard<std::recursive_mutex> lock(_lockmq);
#endif
_messageQueue.emplace_back(std::move(msg));
@@ -261,7 +261,7 @@ bool AsyncEventSourceClient::_queueMessage(AsyncEvent_SharedData_t &&msg) {
void AsyncEventSourceClient::_onAck(size_t len __attribute__((unused)), uint32_t time __attribute__((unused))) {
#ifdef ESP32
// Same here, acquiring the lock early
std::lock_guard<std::mutex> lock(_lockmq);
std::lock_guard<std::recursive_mutex> lock(_lockmq);
#endif
// adjust in-flight len
@@ -290,7 +290,7 @@ void AsyncEventSourceClient::_onPoll() {
if (_messageQueue.size()) {
#ifdef ESP32
// Same here, acquiring the lock early
std::lock_guard<std::mutex> lock(_lockmq);
std::lock_guard<std::recursive_mutex> lock(_lockmq);
#endif
_runQueue();
}
@@ -367,7 +367,7 @@ void AsyncEventSource::_addClient(AsyncEventSourceClient *client) {
return;
}
#ifdef ESP32
std::lock_guard<std::mutex> lock(_client_queue_lock);
std::lock_guard<std::recursive_mutex> lock(_client_queue_lock);
#endif
_clients.emplace_back(client);
if (_connectcb) {
@@ -382,7 +382,7 @@ void AsyncEventSource::_handleDisconnect(AsyncEventSourceClient *client) {
_disconnectcb(client);
}
#ifdef ESP32
std::lock_guard<std::mutex> lock(_client_queue_lock);
std::lock_guard<std::recursive_mutex> lock(_client_queue_lock);
#endif
for (auto i = _clients.begin(); i != _clients.end(); ++i) {
if (i->get() == client) {
@@ -398,10 +398,15 @@ void AsyncEventSource::close() {
// iterator should remain valid even when AsyncEventSource::_handleDisconnect()
// is called very early
#ifdef ESP32
std::lock_guard<std::mutex> lock(_client_queue_lock);
std::lock_guard<std::recursive_mutex> lock(_client_queue_lock);
#endif
for (const auto &c : _clients) {
if (c->connected()) {
/**
* @brief: Fix self-deadlock by using recursive_mutex instead.
* Due to c->close() shall call the callback function _onDisconnect()
* The calling flow _onDisconnect() --> _handleDisconnect() --> deadlock
*/
c->close();
}
}
@@ -412,7 +417,7 @@ size_t AsyncEventSource::avgPacketsWaiting() const {
size_t aql = 0;
uint32_t nConnectedClients = 0;
#ifdef ESP32
std::lock_guard<std::mutex> lock(_client_queue_lock);
std::lock_guard<std::recursive_mutex> lock(_client_queue_lock);
#endif
if (!_clients.size()) {
return 0;
@@ -430,7 +435,7 @@ size_t AsyncEventSource::avgPacketsWaiting() const {
AsyncEventSource::SendStatus AsyncEventSource::send(const char *message, const char *event, uint32_t id, uint32_t reconnect) {
AsyncEvent_SharedData_t shared_msg = std::make_shared<String>(generateEventMessage(message, event, id, reconnect));
#ifdef ESP32
std::lock_guard<std::mutex> lock(_client_queue_lock);
std::lock_guard<std::recursive_mutex> lock(_client_queue_lock);
#endif
size_t hits = 0;
size_t miss = 0;
@@ -446,7 +451,7 @@ AsyncEventSource::SendStatus AsyncEventSource::send(const char *message, const c
size_t AsyncEventSource::count() const {
#ifdef ESP32
std::lock_guard<std::mutex> lock(_client_queue_lock);
std::lock_guard<std::recursive_mutex> lock(_client_queue_lock);
#endif
size_t n_clients{0};
for (const auto &i : _clients) {

View File

@@ -6,8 +6,13 @@
#include <Arduino.h>
#ifdef ESP32
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#ifdef LIBRETINY
#ifdef round
#undef round
#endif
#endif
#include <mutex>
#ifndef SSE_MAX_QUEUED_MESSAGES
#define SSE_MAX_QUEUED_MESSAGES 32
@@ -129,7 +134,7 @@ private:
size_t _max_inflight{SSE_MAX_INFLIGH}; // max num of unacknowledged bytes that could be written to socket buffer
std::list<AsyncEventSourceMessage> _messageQueue;
#ifdef ESP32
mutable std::mutex _lockmq;
mutable std::recursive_mutex _lockmq;
#endif
bool _queueMessage(const char *message, size_t len);
bool _queueMessage(AsyncEvent_SharedData_t &&msg);
@@ -230,7 +235,7 @@ private:
#ifdef ESP32
// Same as for individual messages, protect mutations of _clients list
// since simultaneous access from different tasks is possible
mutable std::mutex _client_queue_lock;
mutable std::recursive_mutex _client_queue_lock;
#endif
ArEventHandlerFunction _connectcb = nullptr;
ArEventHandlerFunction _disconnectcb = nullptr;

View File

@@ -113,43 +113,64 @@ bool AsyncCallbackJsonWebHandler::canHandle(AsyncWebServerRequest *request) cons
void AsyncCallbackJsonWebHandler::handleRequest(AsyncWebServerRequest *request) {
if (_onRequest) {
// GET request:
if (request->method() == HTTP_GET) {
JsonVariant json;
_onRequest(request, json);
return;
} else if (request->_tempObject != NULL) {
}
// POST / PUT / ... requests:
// check if JSON body is too large, if it is, don't deserialize
if (request->contentLength() > _maxContentLength) {
#ifdef ESP32
log_e("Content length exceeds maximum allowed");
#endif
request->send(413);
return;
}
if (request->_tempObject == NULL) {
// there is no body
request->send(400);
return;
}
#if ARDUINOJSON_VERSION_MAJOR == 5
DynamicJsonBuffer jsonBuffer;
JsonVariant json = jsonBuffer.parse((uint8_t *)(request->_tempObject));
JsonVariant json = jsonBuffer.parse((const char *)request->_tempObject);
if (json.success()) {
#elif ARDUINOJSON_VERSION_MAJOR == 6
DynamicJsonDocument jsonBuffer(this->maxJsonBufferSize);
DeserializationError error = deserializeJson(jsonBuffer, (uint8_t *)(request->_tempObject));
DeserializationError error = deserializeJson(jsonBuffer, (const char *)request->_tempObject);
if (!error) {
JsonVariant json = jsonBuffer.as<JsonVariant>();
#else
JsonDocument jsonBuffer;
DeserializationError error = deserializeJson(jsonBuffer, (uint8_t *)(request->_tempObject));
DeserializationError error = deserializeJson(jsonBuffer, (const char *)request->_tempObject);
if (!error) {
JsonVariant json = jsonBuffer.as<JsonVariant>();
#endif
_onRequest(request, json);
return;
}
}
request->send(_contentLength > _maxContentLength ? 413 : 400);
} else {
request->send(500);
// error parsing the body
request->send(400);
}
}
}
void AsyncCallbackJsonWebHandler::handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
if (_onRequest) {
_contentLength = total;
if (total > 0 && request->_tempObject == NULL && total < _maxContentLength) {
request->_tempObject = malloc(total);
// ignore callback if size is larger than maxContentLength
if (total > _maxContentLength) {
return;
}
if (index == 0) {
// this check allows request->_tempObject to be initialized from a middleware
if (request->_tempObject == NULL) {
request->_tempObject = calloc(total + 1, sizeof(uint8_t)); // null-terminated string
if (request->_tempObject == NULL) {
#ifdef ESP32
log_e("Failed to allocate");
@@ -158,8 +179,11 @@ void AsyncCallbackJsonWebHandler::handleBody(AsyncWebServerRequest *request, uin
return;
}
}
}
if (request->_tempObject != NULL) {
memcpy((uint8_t *)(request->_tempObject) + index, data, len);
uint8_t *buffer = (uint8_t *)request->_tempObject;
memcpy(buffer + index, data, len);
}
}
}

View File

@@ -4,8 +4,14 @@
#ifndef ASYNC_JSON_H_
#define ASYNC_JSON_H_
#if __has_include("ArduinoJson.h")
#include <ArduinoJson.h>
#if ARDUINOJSON_VERSION_MAJOR >= 5
#define ASYNC_JSON_SUPPORT 1
#else
#define ASYNC_JSON_SUPPORT 0
#endif // ARDUINOJSON_VERSION_MAJOR >= 5
#endif // __has_include("ArduinoJson.h")
#if ASYNC_JSON_SUPPORT == 1
#include <ESPAsyncWebServer.h>
@@ -73,7 +79,6 @@ protected:
String _uri;
WebRequestMethodComposite _method;
ArJsonRequestHandlerFunction _onRequest;
size_t _contentLength;
#if ARDUINOJSON_VERSION_MAJOR == 6
size_t maxJsonBufferSize;
#endif

View File

@@ -3,30 +3,32 @@
#include <ESPAsyncWebServer.h>
AsyncWebHeader::AsyncWebHeader(const String &data) {
const AsyncWebHeader AsyncWebHeader::parse(const char *data) {
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers
// In HTTP/1.X, a header is a case-insensitive name followed by a colon, then optional whitespace which will be ignored, and finally by its value
if (!data) {
return;
return AsyncWebHeader(); // nullptr
}
int index = data.indexOf(':');
if (index < 0) {
return;
if (data[0] == '\0') {
return AsyncWebHeader(); // empty string
}
_name = data.substring(0, index);
_value = data.substring(index + 2);
}
String AsyncWebHeader::toString() const {
String str;
if (str.reserve(_name.length() + _value.length() + 2)) {
str.concat(_name);
str.concat((char)0x3a);
str.concat((char)0x20);
str.concat(_value);
str.concat(asyncsrv::T_rn);
} else {
#ifdef ESP32
log_e("Failed to allocate");
#endif
}
return str;
if (strchr(data, '\n') || strchr(data, '\r')) {
return AsyncWebHeader(); // Invalid header format
}
const char *colon = strchr(data, ':');
if (!colon) {
return AsyncWebHeader(); // separator not found
}
if (colon == data) {
return AsyncWebHeader(); // Header name cannot be empty
}
const char *startOfValue = colon + 1; // Skip the colon
// skip one optional whitespace after the colon
if (*startOfValue == ' ') {
startOfValue++;
}
String name;
name.reserve(colon - data);
name.concat(data, colon - data);
return AsyncWebHeader(name, String(startOfValue));
}

View File

@@ -0,0 +1,85 @@
#include <ESPAsyncWebServer.h>
/**
* @brief Sends a file from the filesystem to the client, with optional gzip compression and ETag-based caching.
*
* This method serves files over HTTP from the provided filesystem. If a compressed version of the file
* (with a `.gz` extension) exists and uncompressed version does not exist, it serves the compressed file.
* It also handles ETag caching using the CRC32 value from the gzip trailer, responding with `304 Not Modified`
* if the client's `If-None-Match` header matches the generated ETag.
*
* @param fs Reference to the filesystem (SPIFFS, LittleFS, etc.).
* @param path Path to the file to be served.
* @param contentType Optional MIME type of the file to be sent.
* If contentType is "" it will be obtained from the file extension
* @param download If true, forces the file to be sent as a download.
* @param callback Optional template processor for dynamic content generation.
* Templates will not be processed in compressed files.
*
* @note If neither the file nor its compressed version exists, responds with `404 Not Found`.
*/
void AsyncWebServerRequest::send(FS &fs, const String &path, const char *contentType, bool download, AwsTemplateProcessor callback) {
// Check uncompressed file first
if (fs.exists(path)) {
send(beginResponse(fs, path, contentType, download, callback));
return;
}
// Handle compressed version
const String gzPath = path + asyncsrv::T__gz;
File gzFile = fs.open(gzPath, fs::FileOpenMode::read);
// Compressed file not found or invalid
if (!gzFile.seek(gzFile.size() - 8)) {
send(404);
gzFile.close();
return;
}
// ETag validation
if (this->hasHeader(asyncsrv::T_INM)) {
// Generate server ETag from CRC in gzip trailer
uint8_t crcInTrailer[4];
gzFile.read(crcInTrailer, 4);
char serverETag[9];
_getEtag(crcInTrailer, serverETag);
// Compare with client's ETag
const AsyncWebHeader *inmHeader = this->getHeader(asyncsrv::T_INM);
if (inmHeader && inmHeader->value() == serverETag) {
gzFile.close();
this->send(304); // Not Modified
return;
}
}
// Send compressed file response
gzFile.close();
send(beginResponse(fs, path, contentType, download, callback));
}
/**
* @brief Generates an ETag string from a 4-byte trailer
*
* This function converts a 4-byte array into a hexadecimal ETag string enclosed in quotes.
*
* @param trailer[4] Input array of 4 bytes to convert to hexadecimal
* @param serverETag Output buffer to store the ETag
* Must be pre-allocated with minimum 9 bytes (8 hex + 1 null terminator)
*/
void AsyncWebServerRequest::_getEtag(uint8_t trailer[4], char *serverETag) {
static constexpr char hexChars[] = "0123456789ABCDEF";
uint32_t data;
memcpy(&data, trailer, 4);
serverETag[0] = hexChars[(data >> 4) & 0x0F];
serverETag[1] = hexChars[data & 0x0F];
serverETag[2] = hexChars[(data >> 12) & 0x0F];
serverETag[3] = hexChars[(data >> 8) & 0x0F];
serverETag[4] = hexChars[(data >> 20) & 0x0F];
serverETag[5] = hexChars[(data >> 16) & 0x0F];
serverETag[6] = hexChars[(data >> 28)];
serverETag[7] = hexChars[(data >> 24) & 0x0F];
serverETag[8] = '\0';
}

View File

@@ -10,9 +10,9 @@ extern "C" {
/** Major version number (X.x.x) */
#define ASYNCWEBSERVER_VERSION_MAJOR 3
/** Minor version number (x.X.x) */
#define ASYNCWEBSERVER_VERSION_MINOR 7
#define ASYNCWEBSERVER_VERSION_MINOR 8
/** Patch version number (x.x.X) */
#define ASYNCWEBSERVER_VERSION_PATCH 5
#define ASYNCWEBSERVER_VERSION_PATCH 0
/**
* Macro to convert version number into an integer

View File

@@ -17,6 +17,8 @@
#include <rom/ets_sys.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) || defined(ESP8266)
#include <Hash.h>
#elif defined(LIBRETINY)
#include <mbedtls/sha1.h>
#endif
using namespace asyncsrv;
@@ -118,6 +120,11 @@ size_t webSocketSendFrame(AsyncClient *client, bool final, uint8_t opcode, bool
return len;
}
size_t AsyncWebSocketControl::send(AsyncClient *client) {
_finished = true;
return webSocketSendFrame(client, true, _opcode & 0x0F, _mask, _data, _len);
}
/*
* AsyncWebSocketMessageBuffer
*/
@@ -144,65 +151,6 @@ bool AsyncWebSocketMessageBuffer::reserve(size_t size) {
return _buffer->capacity() >= size;
}
/*
* Control Frame
*/
class AsyncWebSocketControl {
private:
uint8_t _opcode;
uint8_t *_data;
size_t _len;
bool _mask;
bool _finished;
public:
AsyncWebSocketControl(uint8_t opcode, const uint8_t *data = NULL, size_t len = 0, bool mask = false)
: _opcode(opcode), _len(len), _mask(len && mask), _finished(false) {
if (data == NULL) {
_len = 0;
}
if (_len) {
if (_len > 125) {
_len = 125;
}
_data = (uint8_t *)malloc(_len);
if (_data == NULL) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
_len = 0;
} else {
memcpy(_data, data, len);
}
} else {
_data = NULL;
}
}
~AsyncWebSocketControl() {
if (_data != NULL) {
free(_data);
}
}
bool finished() const {
return _finished;
}
uint8_t opcode() {
return _opcode;
}
uint8_t len() {
return _len + 2;
}
size_t send(AsyncClient *client) {
_finished = true;
return webSocketSendFrame(client, true, _opcode & 0x0F, _mask, _data, _len);
}
};
/*
* AsyncWebSocketMessage Message
*/
@@ -333,7 +281,7 @@ AsyncWebSocketClient::AsyncWebSocketClient(AsyncWebServerRequest *request, Async
AsyncWebSocketClient::~AsyncWebSocketClient() {
{
#ifdef ESP32
std::lock_guard<std::mutex> lock(_lock);
std::lock_guard<std::recursive_mutex> lock(_lock);
#endif
_messageQueue.clear();
_controlQueue.clear();
@@ -351,7 +299,7 @@ void AsyncWebSocketClient::_onAck(size_t len, uint32_t time) {
_lastMessageTime = millis();
#ifdef ESP32
std::lock_guard<std::mutex> lock(_lock);
std::unique_lock<std::recursive_mutex> lock(_lock);
#endif
if (!_controlQueue.empty()) {
@@ -362,6 +310,14 @@ void AsyncWebSocketClient::_onAck(size_t len, uint32_t time) {
_controlQueue.pop_front();
_status = WS_DISCONNECTED;
if (_client) {
#ifdef ESP32
/*
Unlocking has to be called before return execution otherwise std::unique_lock ::~unique_lock() will get an exception pthread_mutex_unlock.
Due to _client->close(true) shall call the callback function _onDisconnect()
The calling flow _onDisconnect() --> _handleDisconnect() --> ~AsyncWebSocketClient()
*/
lock.unlock();
#endif
_client->close(true);
}
return;
@@ -385,7 +341,7 @@ void AsyncWebSocketClient::_onPoll() {
}
#ifdef ESP32
std::unique_lock<std::mutex> lock(_lock);
std::unique_lock<std::recursive_mutex> lock(_lock);
#endif
if (_client && _client->canSend() && (!_controlQueue.empty() || !_messageQueue.empty())) {
_runQueue();
@@ -415,21 +371,21 @@ void AsyncWebSocketClient::_runQueue() {
bool AsyncWebSocketClient::queueIsFull() const {
#ifdef ESP32
std::lock_guard<std::mutex> lock(_lock);
std::lock_guard<std::recursive_mutex> lock(_lock);
#endif
return (_messageQueue.size() >= WS_MAX_QUEUED_MESSAGES) || (_status != WS_CONNECTED);
}
size_t AsyncWebSocketClient::queueLen() const {
#ifdef ESP32
std::lock_guard<std::mutex> lock(_lock);
std::lock_guard<std::recursive_mutex> lock(_lock);
#endif
return _messageQueue.size();
}
bool AsyncWebSocketClient::canSend() const {
#ifdef ESP32
std::lock_guard<std::mutex> lock(_lock);
std::lock_guard<std::recursive_mutex> lock(_lock);
#endif
return _messageQueue.size() < WS_MAX_QUEUED_MESSAGES;
}
@@ -440,7 +396,7 @@ bool AsyncWebSocketClient::_queueControl(uint8_t opcode, const uint8_t *data, si
}
#ifdef ESP32
std::lock_guard<std::mutex> lock(_lock);
std::lock_guard<std::recursive_mutex> lock(_lock);
#endif
_controlQueue.emplace_back(opcode, data, len, mask);
@@ -458,7 +414,7 @@ bool AsyncWebSocketClient::_queueMessage(AsyncWebSocketSharedBuffer buffer, uint
}
#ifdef ESP32
std::lock_guard<std::mutex> lock(_lock);
std::unique_lock<std::recursive_mutex> lock(_lock);
#endif
if (_messageQueue.size() >= WS_MAX_QUEUED_MESSAGES) {
@@ -466,6 +422,14 @@ bool AsyncWebSocketClient::_queueMessage(AsyncWebSocketSharedBuffer buffer, uint
_status = WS_DISCONNECTED;
if (_client) {
#ifdef ESP32
/*
Unlocking has to be called before return execution otherwise std::unique_lock ::~unique_lock() will get an exception pthread_mutex_unlock.
Due to _client->close(true) shall call the callback function _onDisconnect()
The calling flow _onDisconnect() --> _handleDisconnect() --> ~AsyncWebSocketClient()
*/
lock.unlock();
#endif
_client->close(true);
}
@@ -551,6 +515,7 @@ void AsyncWebSocketClient::_onTimeout(uint32_t time) {
void AsyncWebSocketClient::_onDisconnect() {
// Serial.println("onDis");
_client = nullptr;
_server->_handleDisconnect(this);
}
void AsyncWebSocketClient::_onData(void *pbuf, size_t plen) {
@@ -857,6 +822,16 @@ AsyncWebSocketClient *AsyncWebSocket::_newClient(AsyncWebServerRequest *request)
return &_clients.back();
}
void AsyncWebSocket::_handleDisconnect(AsyncWebSocketClient *client) {
const auto client_id = client->id();
const auto iter = std::find_if(std::begin(_clients), std::end(_clients), [client_id](const AsyncWebSocketClient &c) {
return c.id() == client_id;
});
if (iter != std::end(_clients)) {
_clients.erase(iter);
}
}
bool AsyncWebSocket::availableForWriteAll() {
return std::none_of(std::begin(_clients), std::end(_clients), [](const AsyncWebSocketClient &c) {
return c.queueIsFull();
@@ -1300,11 +1275,20 @@ AsyncWebSocketResponse::AsyncWebSocketResponse(const String &key, AsyncWebSocket
}
k.concat(key);
k.concat(WS_STR_UUID);
#ifdef LIBRETINY
mbedtls_sha1_context ctx;
mbedtls_sha1_init(&ctx);
mbedtls_sha1_starts(&ctx);
mbedtls_sha1_update(&ctx, (const uint8_t *)k.c_str(), k.length());
mbedtls_sha1_finish(&ctx, hash);
mbedtls_sha1_free(&ctx);
#else
SHA1Builder sha1;
sha1.begin();
sha1.add((const uint8_t *)k.c_str(), k.length());
sha1.calculate();
sha1.getBytes(hash);
#endif
#endif
base64_encodestate _state;
base64_init_encodestate(&_state);

View File

@@ -5,8 +5,14 @@
#define ASYNCWEBSOCKET_H_
#include <Arduino.h>
#ifdef ESP32
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#ifdef LIBRETINY
#ifdef round
#undef round
#endif
#endif
#include <mutex>
#ifndef WS_MAX_QUEUED_MESSAGES
#define WS_MAX_QUEUED_MESSAGES 32
@@ -47,7 +53,62 @@ using AsyncWebSocketSharedBuffer = std::shared_ptr<std::vector<uint8_t>>;
class AsyncWebSocket;
class AsyncWebSocketResponse;
class AsyncWebSocketClient;
class AsyncWebSocketControl;
/*
* Control Frame
*/
class AsyncWebSocketControl {
private:
uint8_t _opcode;
uint8_t *_data;
size_t _len;
bool _mask;
bool _finished;
public:
AsyncWebSocketControl(uint8_t opcode, const uint8_t *data = NULL, size_t len = 0, bool mask = false)
: _opcode(opcode), _len(len), _mask(len && mask), _finished(false) {
if (data == NULL) {
_len = 0;
}
if (_len) {
if (_len > 125) {
_len = 125;
}
_data = (uint8_t *)malloc(_len);
if (_data == NULL) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
_len = 0;
} else {
memcpy(_data, data, len);
}
} else {
_data = NULL;
}
}
~AsyncWebSocketControl() {
if (_data != NULL) {
free(_data);
}
}
bool finished() const {
return _finished;
}
uint8_t opcode() {
return _opcode;
}
uint8_t len() {
return _len + 2;
}
size_t send(AsyncClient *client);
};
typedef struct {
/** Message type as defined by enum AwsFrameType.
@@ -152,7 +213,7 @@ private:
uint32_t _clientId;
AwsClientStatus _status;
#ifdef ESP32
mutable std::mutex _lock;
mutable std::recursive_mutex _lock;
#endif
std::deque<AsyncWebSocketControl> _controlQueue;
std::deque<AsyncWebSocketMessage> _messageQueue;
@@ -385,6 +446,7 @@ public:
return _cNextId++;
}
AsyncWebSocketClient *_newClient(AsyncWebServerRequest *request);
void _handleDisconnect(AsyncWebSocketClient *client);
void _handleEvent(AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len);
bool canHandle(AsyncWebServerRequest *request) const override final;
void handleRequest(AsyncWebServerRequest *request) override final;

View File

@@ -15,16 +15,13 @@
#include <unordered_map>
#include <vector>
#ifdef ESP32
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <HTTP_Method.h>
#include <WiFi.h>
#include <http_parser.h>
#else
#error Platform not supported
@@ -138,12 +135,20 @@ private:
String _value;
public:
AsyncWebHeader() {}
AsyncWebHeader(const AsyncWebHeader &) = default;
AsyncWebHeader(AsyncWebHeader &&) = default;
AsyncWebHeader(const char *name, const char *value) : _name(name), _value(value) {}
AsyncWebHeader(const String &name, const String &value) : _name(name), _value(value) {}
AsyncWebHeader(const String &data);
#ifndef ESP8266
[[deprecated("Use AsyncWebHeader::parse(data) instead")]]
#endif
AsyncWebHeader(const String &data)
: AsyncWebHeader(parse(data)){};
AsyncWebHeader &operator=(const AsyncWebHeader &) = default;
AsyncWebHeader &operator=(AsyncWebHeader &&other) = default;
const String &name() const {
return _name;
@@ -151,7 +156,18 @@ public:
const String &value() const {
return _value;
}
String toString() const;
// returns true if the header is valid
operator bool() const {
return _name.length();
}
static const AsyncWebHeader parse(const String &data) {
return parse(data.c_str());
}
static const AsyncWebHeader parse(const char *data);
};
/*
@@ -187,6 +203,7 @@ class AsyncWebServerRequest {
using FS = fs::FS;
friend class AsyncWebServer;
friend class AsyncCallbackWebHandler;
friend class AsyncFileResponse;
private:
AsyncClient *_client;
@@ -258,6 +275,8 @@ private:
void _send();
void _runMiddlewareChain();
static void _getEtag(uint8_t trailer[4], char *serverETag);
public:
File _tempFile;
void *_tempObject;
@@ -370,13 +389,7 @@ public:
send(beginResponse(code, contentType, content, len, callback));
}
void send(FS &fs, const String &path, const char *contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr) {
if (fs.exists(path) || (!download && fs.exists(path + asyncsrv::T__gz))) {
send(beginResponse(fs, path, contentType, download, callback));
} else {
send(404);
}
}
void send(FS &fs, const String &path, const char *contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr);
void send(FS &fs, const String &path, const String &contentType, bool download = false, AwsTemplateProcessor callback = nullptr) {
send(fs, path, contentType.c_str(), download, callback);
}
@@ -1041,6 +1054,10 @@ public:
setContentType(type.c_str());
}
void setContentType(const char *type);
bool addHeader(AsyncWebHeader &&header, bool replaceExisting = true);
bool addHeader(const AsyncWebHeader &header, bool replaceExisting = true) {
return header && addHeader(header.name(), header.value(), replaceExisting);
}
bool addHeader(const char *name, const char *value, bool replaceExisting = true);
bool addHeader(const String &name, const String &value, bool replaceExisting = true) {
return addHeader(name.c_str(), value.c_str(), replaceExisting);

View File

@@ -172,7 +172,11 @@ void AsyncLoggingMiddleware::run(AsyncWebServerRequest *request, ArMiddlewareNex
return;
}
_out->print(F("* Connection from "));
#ifndef LIBRETINY
_out->print(request->client()->remoteIP().toString());
#else
_out->print(request->client()->remoteIP());
#endif
_out->print(':');
_out->println(request->client()->remotePort());
_out->print('>');

View File

@@ -209,11 +209,14 @@ void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest *request) {
char buf[len];
char *ret = lltoa(lw ^ request->_tempFile.size(), buf, len, 10);
etag = ret ? String(ret) : String(request->_tempFile.size());
#elif defined(LIBRETINY)
long val = lw ^ request->_tempFile.size();
etag = String(val);
#else
etag = lw ^ request->_tempFile.size(); // etag combines file size and lastmod timestamp
#endif
} else {
#if defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#if defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) || defined(LIBRETINY)
etag = String(request->_tempFile.size());
#else
etag = request->_tempFile.size();

View File

@@ -22,10 +22,10 @@ enum {
};
AsyncWebServerRequest::AsyncWebServerRequest(AsyncWebServer *s, AsyncClient *c)
: _client(c), _server(s), _handler(NULL), _response(NULL), _temp(), _parseState(PARSE_REQ_START), _version(0), _method(HTTP_ANY), _url(), _host(),
_contentType(), _boundary(), _authorization(), _reqconntype(RCT_HTTP), _authMethod(AsyncAuthType::AUTH_NONE), _isMultipart(false), _isPlainPost(false),
_expectingContinue(false), _contentLength(0), _parsedLength(0), _multiParseState(0), _boundaryPosition(0), _itemStartIndex(0), _itemSize(0), _itemName(),
_itemFilename(), _itemType(), _itemValue(), _itemBuffer(0), _itemBufferIndex(0), _itemIsFile(false), _tempObject(NULL) {
: _client(c), _server(s), _handler(NULL), _response(NULL), _onDisconnectfn(NULL), _temp(), _parseState(PARSE_REQ_START), _version(0), _method(HTTP_ANY),
_url(), _host(), _contentType(), _boundary(), _authorization(), _reqconntype(RCT_HTTP), _authMethod(AsyncAuthType::AUTH_NONE), _isMultipart(false),
_isPlainPost(false), _expectingContinue(false), _contentLength(0), _parsedLength(0), _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;
@@ -341,10 +341,10 @@ bool AsyncWebServerRequest::_parseReqHead() {
}
bool AsyncWebServerRequest::_parseReqHeader() {
int index = _temp.indexOf(':');
if (index) {
String name(_temp.substring(0, index));
String value(_temp.substring(index + 2));
AsyncWebHeader header = AsyncWebHeader::parse(_temp);
if (header) {
const String &name = header.name();
const String &value = header.value();
if (name.equalsIgnoreCase(T_Host)) {
_host = value;
} else if (name.equalsIgnoreCase(T_Content_Type)) {
@@ -392,9 +392,9 @@ bool AsyncWebServerRequest::_parseReqHeader() {
_reqconntype = RCT_EVENT;
}
}
_headers.emplace_back(name, value);
_headers.emplace_back(std::move(header));
}
#if defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#if defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) || defined(LIBRETINY)
// Ancient PRI core does not have String::clear() method 8-()
_temp = emptyString;
#else
@@ -419,7 +419,7 @@ void AsyncWebServerRequest::_parsePlainPostChar(uint8_t data) {
_params.emplace_back(name, urlDecode(value), true);
}
#if defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#if defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) || defined(LIBRETINY)
// Ancient PRI core does not have String::clear() method 8-()
_temp = emptyString;
#else

View File

@@ -75,7 +75,6 @@ class AsyncFileResponse : public AsyncAbstractResponse {
private:
File _content;
String _path;
void _setContentTypeFromPath(const String &path);
public:

View File

@@ -6,17 +6,6 @@
using namespace asyncsrv;
// 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
*
@@ -134,6 +123,30 @@ bool AsyncWebServerResponse::headerMustBePresentOnce(const String &name) {
return false;
}
bool AsyncWebServerResponse::addHeader(AsyncWebHeader &&header, bool replaceExisting) {
if (!header) {
return false; // invalid header
}
for (auto i = _headers.begin(); i != _headers.end(); ++i) {
if (i->name().equalsIgnoreCase(header.name())) {
// header already set
if (replaceExisting) {
// remove, break and add the new one
_headers.erase(i);
break;
} else if (headerMustBePresentOnce(i->name())) { // we can have only one header with that name
// do not update
return false;
} else {
break; // accept multiple headers with the same name
}
}
}
// header was not found found, or existing one was removed
_headers.emplace_back(std::move(header));
return true;
}
bool AsyncWebServerResponse::addHeader(const char *name, const char *value, bool replaceExisting) {
for (auto i = _headers.begin(); i != _headers.end(); ++i) {
if (i->name().equalsIgnoreCase(name)) {
@@ -595,6 +608,16 @@ size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t *data, size
* File Response
* */
/**
* @brief Sets the content type based on the file path extension
*
* This method determines the appropriate MIME content type for a file based on its
* file extension. It supports both external content type functions (if available)
* and an internal mapping of common file extensions to their corresponding MIME types.
*
* @param path The file path string from which to extract the extension
* @note The method modifies the internal _contentType member variable
*/
void AsyncFileResponse::_setContentTypeFromPath(const String &path) {
#if HAVE_EXTERN_GET_Content_Type_FUNCTION
#ifndef ESP8266
@@ -604,90 +627,138 @@ void AsyncFileResponse::_setContentTypeFromPath(const String &path) {
#endif
_contentType = getContentType(path);
#else
if (path.endsWith(T__html)) {
const char *cpath = path.c_str();
const char *dot = strrchr(cpath, '.');
if (!dot) {
_contentType = T_application_octet_stream;
return;
}
if (strcmp(dot, T__html) == 0 || strcmp(dot, T__htm) == 0) {
_contentType = T_text_html;
} else if (path.endsWith(T__htm)) {
_contentType = T_text_html;
} else if (path.endsWith(T__css)) {
} else if (strcmp(dot, T__css) == 0) {
_contentType = T_text_css;
} else if (path.endsWith(T__json)) {
_contentType = T_application_json;
} else if (path.endsWith(T__js)) {
} else if (strcmp(dot, T__js) == 0) {
_contentType = T_application_javascript;
} else if (path.endsWith(T__png)) {
} else if (strcmp(dot, T__json) == 0) {
_contentType = T_application_json;
} else if (strcmp(dot, T__png) == 0) {
_contentType = T_image_png;
} else if (path.endsWith(T__gif)) {
_contentType = T_image_gif;
} else if (path.endsWith(T__jpg)) {
_contentType = T_image_jpeg;
} else if (path.endsWith(T__ico)) {
} else if (strcmp(dot, T__ico) == 0) {
_contentType = T_image_x_icon;
} else if (path.endsWith(T__svg)) {
} else if (strcmp(dot, T__svg) == 0) {
_contentType = T_image_svg_xml;
} else if (path.endsWith(T__eot)) {
_contentType = T_font_eot;
} else if (path.endsWith(T__woff)) {
_contentType = T_font_woff;
} else if (path.endsWith(T__woff2)) {
} else if (strcmp(dot, T__jpg) == 0) {
_contentType = T_image_jpeg;
} else if (strcmp(dot, T__webp) == 0) {
_contentType = T_image_webp;
} else if (strcmp(dot, T__avif) == 0) {
_contentType = T_image_avif;
} else if (strcmp(dot, T__gif) == 0) {
_contentType = T_image_gif;
} else if (strcmp(dot, T__woff2) == 0) {
_contentType = T_font_woff2;
} else if (path.endsWith(T__ttf)) {
} else if (strcmp(dot, T__woff) == 0) {
_contentType = T_font_woff;
} else if (strcmp(dot, T__ttf) == 0) {
_contentType = T_font_ttf;
} else if (path.endsWith(T__xml)) {
} else if (strcmp(dot, T__xml) == 0) {
_contentType = T_text_xml;
} else if (path.endsWith(T__pdf)) {
} else if (strcmp(dot, T__pdf) == 0) {
_contentType = T_application_pdf;
} else if (path.endsWith(T__zip)) {
_contentType = T_application_zip;
} else if (path.endsWith(T__gz)) {
_contentType = T_application_x_gzip;
} else {
} else if (strcmp(dot, T__mp4) == 0) {
_contentType = T_video_mp4;
} else if (strcmp(dot, T__opus) == 0) {
_contentType = T_audio_opus;
} else if (strcmp(dot, T__webm) == 0) {
_contentType = T_video_webm;
} else if (strcmp(dot, T__txt) == 0) {
_contentType = T_text_plain;
} else {
_contentType = T_application_octet_stream;
}
#endif
}
/**
* @brief Constructor for AsyncFileResponse that handles file serving with compression support
*
* This constructor creates an AsyncFileResponse object that can serve files from a filesystem,
* with automatic fallback to gzip-compressed versions if the original file is not found.
* It also handles ETag generation for caching and supports both inline and download modes.
*
* @param fs Reference to the filesystem object used to open files
* @param path Path to the file to be served (without compression extension)
* @param contentType MIME type of the file content (empty string for auto-detection)
* @param download If true, file will be served as download attachment; if false, as inline content
* @param callback Template processor callback for dynamic content processing
*/
AsyncFileResponse::AsyncFileResponse(FS &fs, const String &path, const char *contentType, bool download, AwsTemplateProcessor callback)
: AsyncAbstractResponse(callback) {
_code = 200;
_path = path;
if (!download && !fs.exists(_path) && fs.exists(_path + T__gz)) {
_path = _path + T__gz;
// Try to open the uncompressed version first
_content = fs.open(path, fs::FileOpenMode::read);
if (_content.available()) {
_contentLength = _content.size();
} else {
// Try to open the compressed version (.gz)
String gzPath;
uint16_t pathLen = path.length();
gzPath.reserve(pathLen + 3);
gzPath.concat(path);
gzPath.concat(asyncsrv::T__gz);
_content = fs.open(gzPath, fs::FileOpenMode::read);
_contentLength = _content.size();
if (_content.seek(_contentLength - 8)) {
addHeader(T_Content_Encoding, T_gzip, false);
_callback = nullptr; // Unable to process zipped templates
_sendContentLength = true;
_chunked = false;
// Add ETag and cache headers
uint8_t crcInTrailer[4];
_content.read(crcInTrailer, sizeof(crcInTrailer));
char serverETag[9];
AsyncWebServerRequest::_getEtag(crcInTrailer, serverETag);
addHeader(T_ETag, serverETag, true);
addHeader(T_Cache_Control, T_no_cache, true);
_content.seek(0);
} else {
// File is corrupted or invalid
_code = 404;
return;
}
}
_content = fs.open(_path, fs::FileOpenMode::read);
_contentLength = _content.size();
if (strlen(contentType) == 0) {
if (*contentType == '\0') {
_setContentTypeFromPath(path);
} else {
_contentType = contentType;
}
if (download) {
// Extract filename from path and set as download attachment
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"));
}
snprintf(buf, sizeof(buf), T_attachment, filename);
addHeader(T_Content_Disposition, buf, false);
} else {
// Serve file inline (display in browser)
addHeader(T_Content_Disposition, T_inline, false);
}
_code = 200;
}
AsyncFileResponse::AsyncFileResponse(File content, const String &path, const char *contentType, bool download, AwsTemplateProcessor callback)
: AsyncAbstractResponse(callback) {
_code = 200;
_path = path;
if (!download && String(content.name()).endsWith(T__gz) && !path.endsWith(T__gz)) {
if (String(content.name()).endsWith(T__gz) && !path.endsWith(T__gz)) {
addHeader(T_Content_Encoding, T_gzip, false);
_callback = nullptr; // Unable to process gzipped templates
_sendContentLength = true;
@@ -822,7 +893,7 @@ AsyncResponseStream::AsyncResponseStream(const char *contentType, size_t bufferS
_contentType = contentType;
// internal buffer will be null on allocation failure
_content = std::unique_ptr<cbuf>(new cbuf(bufferSize));
if (_content->size() != bufferSize) {
if (bufferSize && _content->size() < bufferSize) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
@@ -840,6 +911,14 @@ size_t AsyncResponseStream::write(const uint8_t *data, size_t len) {
if (len > _content->room()) {
size_t needed = len - _content->room();
_content->resizeAdd(needed);
// log a warning if allocation failed, but do not return: keep writing the bytes we can
// with _content->write: if len is more than the available size in the buffer, only
// the available size will be written
if (len > _content->room()) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
}
}
size_t written = _content->write((const char *)data, len);
_contentLength += written;

View File

@@ -4,10 +4,18 @@
#include "ESPAsyncWebServer.h"
#include "WebHandlerImpl.h"
#if defined(ESP32) || defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) || defined(LIBRETINY)
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#else
#error Platform not supported
#endif
using namespace asyncsrv;
bool ON_STA_FILTER(AsyncWebServerRequest *request) {
#ifndef CONFIG_IDF_TARGET_ESP32H2
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
return WiFi.localIP() == request->client()->localIP();
#else
return false;
@@ -15,7 +23,7 @@ bool ON_STA_FILTER(AsyncWebServerRequest *request) {
}
bool ON_AP_FILTER(AsyncWebServerRequest *request) {
#ifndef CONFIG_IDF_TARGET_ESP32H2
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
return WiFi.localIP() != request->client()->localIP();
#else
return false;

View File

@@ -10,39 +10,39 @@ static constexpr const char *empty = "";
static constexpr const char *T__opaque = "\", opaque=\"";
static constexpr const char *T_100_CONTINUE = "100-continue";
static constexpr const char *T_13 = "13";
static constexpr const char *T_ACCEPT = "accept";
static constexpr const char *T_Accept_Ranges = "accept-ranges";
static constexpr const char *T_app_xform_urlencoded = "application/x-www-form-urlencoded";
static constexpr const char *T_AUTH = "authorization";
static constexpr const char *T_ACCEPT = "Accept";
static constexpr const char *T_Accept_Ranges = "Accept-Ranges";
static constexpr const char *T_attachment = "attachment; filename=\"%s\"";
static constexpr const char *T_AUTH = "Authorization";
static constexpr const char *T_auth_nonce = "\", qop=\"auth\", nonce=\"";
static constexpr const char *T_BASIC = "basic";
static constexpr const char *T_BASIC_REALM = "basic realm=\"";
static constexpr const char *T_BEARER = "bearer";
static constexpr const char *T_BASIC = "Basic";
static constexpr const char *T_BASIC_REALM = "Basic realm=\"";
static constexpr const char *T_BEARER = "Bearer";
static constexpr const char *T_BODY = "body";
static constexpr const char *T_Cache_Control = "cache-control";
static constexpr const char *T_Cache_Control = "Cache-Control";
static constexpr const char *T_chunked = "chunked";
static constexpr const char *T_close = "close";
static constexpr const char *T_cnonce = "cnonce";
static constexpr const char *T_Connection = "connection";
static constexpr const char *T_Content_Disposition = "content-disposition";
static constexpr const char *T_Content_Encoding = "content-encoding";
static constexpr const char *T_Content_Length = "content-length";
static constexpr const char *T_Content_Type = "content-type";
static constexpr const char *T_Content_Location = "content-location";
static constexpr const char *T_Cookie = "cookie";
static constexpr const char *T_CORS_ACAC = "access-control-allow-credentials";
static constexpr const char *T_CORS_ACAH = "access-control-allow-headers";
static constexpr const char *T_CORS_ACAM = "access-control-allow-methods";
static constexpr const char *T_CORS_ACAO = "access-control-allow-origin";
static constexpr const char *T_CORS_ACMA = "access-control-max-age";
static constexpr const char *T_CORS_O = "origin";
static constexpr const char *T_Connection = "Connection";
static constexpr const char *T_Content_Disposition = "Content-Disposition";
static constexpr const char *T_Content_Encoding = "Content-Encoding";
static constexpr const char *T_Content_Length = "Content-Length";
static constexpr const char *T_Content_Type = "Content-Type";
static constexpr const char *T_Content_Location = "Content-Location";
static constexpr const char *T_Cookie = "Cookie";
static constexpr const char *T_CORS_ACAC = "Access-Control-Allow-Credentials";
static constexpr const char *T_CORS_ACAH = "Access-Control-Allow-Headers";
static constexpr const char *T_CORS_ACAM = "Access-Control-Allow-Methods";
static constexpr const char *T_CORS_ACAO = "Access-Control-Allow-Origin";
static constexpr const char *T_CORS_ACMA = "Access-Control-Max-Age";
static constexpr const char *T_CORS_O = "Origin";
static constexpr const char *T_data_ = "data: ";
static constexpr const char *T_Date = "date";
static constexpr const char *T_DIGEST = "digest";
static constexpr const char *T_DIGEST_ = "digest ";
static constexpr const char *T_ETag = "etag";
static constexpr const char *T_Date = "Date";
static constexpr const char *T_DIGEST = "Digest";
static constexpr const char *T_DIGEST_ = "Digest ";
static constexpr const char *T_ETag = "ETag";
static constexpr const char *T_event_ = "event: ";
static constexpr const char *T_EXPECT = "expect";
static constexpr const char *T_EXPECT = "Expect";
static constexpr const char *T_FALSE = "false";
static constexpr const char *T_filename = "filename";
static constexpr const char *T_gzip = "gzip";
@@ -50,12 +50,13 @@ static constexpr const char *T_Host = "host";
static constexpr const char *T_HTTP_1_0 = "HTTP/1.0";
static constexpr const char *T_HTTP_100_CONT = "HTTP/1.1 100 Continue\r\n\r\n";
static constexpr const char *T_id__ = "id: ";
static constexpr const char *T_IMS = "if-modified-since";
static constexpr const char *T_INM = "if-none-match";
static constexpr const char *T_IMS = "If-Modified-Since";
static constexpr const char *T_INM = "If-None-Match";
static constexpr const char *T_inline = "inline";
static constexpr const char *T_keep_alive = "keep-alive";
static constexpr const char *T_Last_Event_ID = "last-event-id";
static constexpr const char *T_Last_Modified = "last-modified";
static constexpr const char *T_LOCATION = "location";
static constexpr const char *T_Last_Event_ID = "Last-Event-ID";
static constexpr const char *T_Last_Modified = "Last-Modified";
static constexpr const char *T_LOCATION = "Location";
static constexpr const char *T_LOGIN_REQ = "Login Required";
static constexpr const char *T_MULTIPART_ = "multipart/";
static constexpr const char *T_name = "name";
@@ -69,21 +70,20 @@ static constexpr const char *T_realm = "realm";
static constexpr const char *T_realm__ = "realm=\"";
static constexpr const char *T_response = "response";
static constexpr const char *T_retry_ = "retry: ";
static constexpr const char *T_retry_after = "retry-after";
static constexpr const char *T_retry_after = "Retry-After";
static constexpr const char *T_nn = "\n\n";
static constexpr const char *T_rn = "\r\n";
static constexpr const char *T_rnrn = "\r\n\r\n";
static constexpr const char *T_Server = "server";
static constexpr const char *T_Transfer_Encoding = "transfer-encoding";
static constexpr const char *T_Server = "Server";
static constexpr const char *T_Transfer_Encoding = "Transfer-Encoding";
static constexpr const char *T_TRUE = "true";
static constexpr const char *T_UPGRADE = "upgrade";
static constexpr const char *T_UPGRADE = "Upgrade";
static constexpr const char *T_uri = "uri";
static constexpr const char *T_username = "username";
static constexpr const char *T_WS = "websocket";
static constexpr const char *T_WWW_AUTH = "www-authenticate";
static constexpr const char *T_WWW_AUTH = "WWW-Authenticate";
// HTTP Methods
static constexpr const char *T_ANY = "ANY";
static constexpr const char *T_GET = "GET";
static constexpr const char *T_POST = "POST";
@@ -103,44 +103,55 @@ static constexpr const char *T_RCT_EVENT = "RCT_EVENT";
static constexpr const char *T_ERROR = "ERROR";
// extensions & MIME-Types
static constexpr const char *T__css = ".css";
static constexpr const char *T__eot = ".eot";
static constexpr const char *T__gif = ".gif";
static constexpr const char *T__gz = ".gz";
static constexpr const char *T__htm = ".htm";
static constexpr const char *T__html = ".html";
static constexpr const char *T__ico = ".ico";
static constexpr const char *T__jpg = ".jpg";
static constexpr const char *T__js = ".js";
static constexpr const char *T__json = ".json";
static constexpr const char *T__pdf = ".pdf";
static constexpr const char *T__png = ".png";
static constexpr const char *T__svg = ".svg";
static constexpr const char *T__ttf = ".ttf";
static constexpr const char *T__woff = ".woff";
static constexpr const char *T__woff2 = ".woff2";
static constexpr const char *T__xml = ".xml";
static constexpr const char *T__zip = ".zip";
static constexpr const char *T_application_javascript = "application/javascript";
static constexpr const char *T__avif = ".avif"; // AVIF: Highly compressed images. Compatible with all modern browsers.
static constexpr const char *T__csv = ".csv"; // CSV: Data logging and configuration
static constexpr const char *T__css = ".css"; // CSS: Styling for web interfaces
static constexpr const char *T__gif = ".gif"; // GIF: Simple animations. Legacy support
static constexpr const char *T__gz = ".gz"; // GZ: compressed files
static constexpr const char *T__htm = ".htm"; // HTM: Web interface files
static constexpr const char *T__html = ".html"; // HTML: Web interface files
static constexpr const char *T__ico = ".ico"; // ICO: Favicons, system icons. Legacy support
static constexpr const char *T__jpg = ".jpg"; // JPEG/JPG: Photos. Legacy support
static constexpr const char *T__js = ".js"; // JavaScript: Interactive functionality
static constexpr const char *T__json = ".json"; // JSON: Data exchange format
static constexpr const char *T__mp4 = ".mp4"; // MP4: Proprietary format. Worse compression than WEBM.
static constexpr const char *T__opus = ".opus"; // OPUS: High compression audio format
static constexpr const char *T__pdf = ".pdf"; // PDF: Universal document format
static constexpr const char *T__png = ".png"; // PNG: Icons, logos, transparency. Legacy support
static constexpr const char *T__svg = ".svg"; // SVG: Vector graphics, icons (scalable, tiny file sizes)
static constexpr const char *T__ttf = ".ttf"; // TTF: Font file. Legacy support
static constexpr const char *T__txt = ".txt"; // TXT: Plain text files
static constexpr const char *T__webm = ".webm"; // WebM: Video. Open source, optimized for web. Compatible with all modern browsers.
static constexpr const char *T__webp = ".webp"; // WebP: Highly compressed images. Compatible with all modern browsers.
static constexpr const char *T__woff = ".woff"; // WOFF: Font file. Legacy support
static constexpr const char *T__woff2 = ".woff2"; // WOFF2: Better compression. Compatible with all modern browsers.
static constexpr const char *T__xml = ".xml"; // XML: Configuration and data files
static constexpr const char *T_application_javascript = "application/javascript"; // Obsolete type for JavaScript
static constexpr const char *T_application_json = "application/json";
static constexpr const char *T_application_msgpack = "application/msgpack";
static constexpr const char *T_application_octet_stream = "application/octet-stream";
static constexpr const char *T_application_pdf = "application/pdf";
static constexpr const char *T_application_x_gzip = "application/x-gzip";
static constexpr const char *T_application_zip = "application/zip";
static constexpr const char *T_font_eot = "font/eot";
static constexpr const char *T_app_xform_urlencoded = "application/x-www-form-urlencoded";
static constexpr const char *T_audio_opus = "audio/opus";
static constexpr const char *T_font_ttf = "font/ttf";
static constexpr const char *T_font_woff = "font/woff";
static constexpr const char *T_font_woff2 = "font/woff2";
static constexpr const char *T_image_avif = "image/avif";
static constexpr const char *T_image_gif = "image/gif";
static constexpr const char *T_image_jpeg = "image/jpeg";
static constexpr const char *T_image_png = "image/png";
static constexpr const char *T_image_svg_xml = "image/svg+xml";
static constexpr const char *T_image_webp = "image/webp";
static constexpr const char *T_image_x_icon = "image/x-icon";
static constexpr const char *T_text_css = "text/css";
static constexpr const char *T_text_csv = "text/csv";
static constexpr const char *T_text_event_stream = "text/event-stream";
static constexpr const char *T_text_html = "text/html";
static constexpr const char *T_text_javascript = "text/javascript";
static constexpr const char *T_text_plain = "text/plain";
static constexpr const char *T_text_xml = "text/xml";
static constexpr const char *T_video_mp4 = "video/mp4";
static constexpr const char *T_video_webm = "video/webm";
// Response codes
static constexpr const char *T_HTTP_CODE_100 = "Continue";
@@ -175,7 +186,7 @@ static constexpr const char *T_HTTP_CODE_412 = "Precondition Failed";
static constexpr const char *T_HTTP_CODE_413 = "Request Entity Too Large";
static constexpr const char *T_HTTP_CODE_414 = "Request-URI Too Large";
static constexpr const char *T_HTTP_CODE_415 = "Unsupported Media Type";
static constexpr const char *T_HTTP_CODE_416 = "Requested range not satisfiable";
static constexpr const char *T_HTTP_CODE_416 = "Requested Range Not Satisfiable";
static constexpr const char *T_HTTP_CODE_417 = "Expectation Failed";
static constexpr const char *T_HTTP_CODE_429 = "Too Many Requests";
static constexpr const char *T_HTTP_CODE_500 = "Internal Server Error";
@@ -183,11 +194,14 @@ static constexpr const char *T_HTTP_CODE_501 = "Not Implemented";
static constexpr const char *T_HTTP_CODE_502 = "Bad Gateway";
static constexpr const char *T_HTTP_CODE_503 = "Service Unavailable";
static constexpr const char *T_HTTP_CODE_504 = "Gateway Time-out";
static constexpr const char *T_HTTP_CODE_505 = "HTTP Version not supported";
static constexpr const char *T_HTTP_CODE_505 = "HTTP Version Not Supported";
static constexpr const char *T_HTTP_CODE_ANY = "Unknown code";
static constexpr const uint8_t T_only_once_headers_len = 11;
static constexpr const char *T_only_once_headers[] = {T_Content_Length, T_Content_Type, T_Date, T_ETag, T_Last_Modified, T_LOCATION, T_retry_after,
T_Transfer_Encoding, T_Content_Location, T_Server, T_WWW_AUTH};
static constexpr const char *T_only_once_headers[] = {
T_Accept_Ranges, T_Content_Length, T_Content_Type, T_Connection, T_CORS_ACAC, T_CORS_ACAH, T_CORS_ACAM, T_CORS_ACAO,
T_CORS_ACMA, T_CORS_O, T_Date, T_DIGEST, T_ETag, T_Last_Modified, T_LOCATION, T_retry_after,
T_Transfer_Encoding, T_Content_Location, T_Server, T_WWW_AUTH
};
static constexpr size_t T_only_once_headers_len = sizeof(T_only_once_headers) / sizeof(T_only_once_headers[0]);
} // namespace asyncsrv