diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..8f47348
--- /dev/null
+++ b/.clang-format
@@ -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
diff --git a/.codespellrc b/.codespellrc
new file mode 100644
index 0000000..d26ee41
--- /dev/null
+++ b/.codespellrc
@@ -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 =
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..e22936c
--- /dev/null
+++ b/.editorconfig
@@ -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
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1efbc8e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+.DS_Store
+.lh
+/.pio
+/.vscode
+/logs
diff --git a/.gitpod.Dockerfile b/.gitpod.Dockerfile
new file mode 100644
index 0000000..29eeb43
--- /dev/null
+++ b/.gitpod.Dockerfile
@@ -0,0 +1,2 @@
+FROM gitpod/workspace-python-3.11
+USER gitpod
diff --git a/.gitpod.yml b/.gitpod.yml
new file mode 100644
index 0000000..2f8a443
--- /dev/null
+++ b/.gitpod.yml
@@ -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
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 0000000..eb2d62e
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -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$
diff --git a/README.md b/README.md
index 9bb7575..1db7aee 100644
--- a/README.md
+++ b/README.md
@@ -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**
diff --git a/data/README.md b/data/README.md
deleted file mode 100644
index 96a2ee4..0000000
--- a/data/README.md
+++ /dev/null
@@ -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.
-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.
-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.
-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.
-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.
-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.
-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.
-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.
diff --git a/examples/AsyncResponseStream/AsyncResponseStream.ino b/examples/AsyncResponseStream/AsyncResponseStream.ino
index 62fa799..451bb1a 100644
--- a/examples/AsyncResponseStream/AsyncResponseStream.ino
+++ b/examples/AsyncResponseStream/AsyncResponseStream.ino
@@ -2,7 +2,7 @@
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
#include
-#ifdef ESP32
+#if defined(ESP32) || defined(LIBRETINY)
#include
#include
#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
diff --git a/examples/AsyncTunnel/AsyncTunnel.ino b/examples/AsyncTunnel/AsyncTunnel.ino
new file mode 100644
index 0000000..b63c056
--- /dev/null
+++ b/examples/AsyncTunnel/AsyncTunnel.ino
@@ -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
+#if defined(ESP32) || defined(LIBRETINY)
+#include
+#include
+#elif defined(ESP8266)
+#include
+#include
+#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
+#include
+#include
+#endif
+
+#include
+
+#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"(
+
+
+
+ WebSocket Tunnel Example
+
+
+ WebSocket Tunnel Example
+
+ Fetch
+
+
+
+
+ )";
+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 *safeAsyncClient = new std::shared_ptr(std::make_shared());
+ 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 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 *)pb->payload, (uint8_t *)pb->payload + pb->len), [=](std::vector *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);
+}
diff --git a/examples/Auth/Auth.ino b/examples/Auth/Auth.ino
index c3751e0..8f5b535 100644
--- a/examples/Auth/Auth.ino
+++ b/examples/Auth/Auth.ino
@@ -6,7 +6,7 @@
//
#include
-#ifdef ESP32
+#if defined(ESP32) || defined(LIBRETINY)
#include
#include
#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
diff --git a/examples/CORS/CORS.ino b/examples/CORS/CORS.ino
index 3be46fd..647d555 100644
--- a/examples/CORS/CORS.ino
+++ b/examples/CORS/CORS.ino
@@ -6,7 +6,7 @@
//
#include
-#ifdef ESP32
+#if defined(ESP32) || defined(LIBRETINY)
#include
#include
#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
diff --git a/examples/CaptivePortal/CaptivePortal.ino b/examples/CaptivePortal/CaptivePortal.ino
index a872a9b..0b8c317 100644
--- a/examples/CaptivePortal/CaptivePortal.ino
+++ b/examples/CaptivePortal/CaptivePortal.ino
@@ -2,7 +2,7 @@
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
#include
-#ifdef ESP32
+#if defined(ESP32) || defined(LIBRETINY)
#include
#include
#elif defined(ESP8266)
@@ -28,7 +28,7 @@ public:
response->print("Captive Portal ");
response->print("This is our captive portal front page.
");
response->printf("You were trying to reach: http://%s%s
", 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("Try opening this link instead
", WiFi.softAPIP().toString().c_str());
#endif
response->print("");
@@ -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);
diff --git a/examples/CatchAllHandler/CatchAllHandler.ino b/examples/CatchAllHandler/CatchAllHandler.ino
index 42a3698..fb01410 100644
--- a/examples/CatchAllHandler/CatchAllHandler.ino
+++ b/examples/CatchAllHandler/CatchAllHandler.ino
@@ -6,7 +6,7 @@
//
#include
-#ifdef ESP32
+#if defined(ESP32) || defined(LIBRETINY)
#include
#include
#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
diff --git a/examples/ChunkResponse/ChunkResponse.ino b/examples/ChunkResponse/ChunkResponse.ino
index e7d4838..52c31c0 100644
--- a/examples/ChunkResponse/ChunkResponse.ino
+++ b/examples/ChunkResponse/ChunkResponse.ino
@@ -6,7 +6,7 @@
//
#include
-#ifdef ESP32
+#if defined(ESP32) || defined(LIBRETINY)
#include
#include
#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
diff --git a/examples/ChunkRetryResponse/ChunkRetryResponse.ino b/examples/ChunkRetryResponse/ChunkRetryResponse.ino
index 48772cc..4e67edb 100644
--- a/examples/ChunkRetryResponse/ChunkRetryResponse.ino
+++ b/examples/ChunkRetryResponse/ChunkRetryResponse.ino
@@ -6,7 +6,7 @@
//
#include
-#ifdef ESP32
+#if defined(ESP32) || defined(LIBRETINY)
#include
#include
#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();
key = -1;
diff --git a/examples/EndBegin/EndBegin.ino b/examples/EndBegin/EndBegin.ino
index acfc6ff..8e91fcf 100644
--- a/examples/EndBegin/EndBegin.ino
+++ b/examples/EndBegin/EndBegin.ino
@@ -6,7 +6,7 @@
//
#include
-#ifdef ESP32
+#if defined(ESP32) || defined(LIBRETINY)
#include
#include
#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
diff --git a/examples/Filters/Filters.ino b/examples/Filters/Filters.ino
index 519478c..bcdb5b3 100644
--- a/examples/Filters/Filters.ino
+++ b/examples/Filters/Filters.ino
@@ -6,7 +6,7 @@
//
#include
-#ifdef ESP32
+#if defined(ESP32) || defined(LIBRETINY)
#include
#include
#elif defined(ESP8266)
@@ -32,7 +32,7 @@ public:
response->print("Captive Portal ");
response->print("This is out captive portal front page.
");
response->printf("You were trying to reach: http://%s%s
", 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("Try opening this link instead
", WiFi.softAPIP().toString().c_str());
#endif
response->print("");
@@ -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) {
diff --git a/examples/FlashResponse/FlashResponse.ino b/examples/FlashResponse/FlashResponse.ino
index 6948cd2..5763f22 100644
--- a/examples/FlashResponse/FlashResponse.ino
+++ b/examples/FlashResponse/FlashResponse.ino
@@ -6,7 +6,7 @@
//
#include
-#ifdef ESP32
+#if defined(ESP32) || defined(LIBRETINY)
#include
#include
#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
diff --git a/examples/HeaderManipulation/HeaderManipulation.ino b/examples/HeaderManipulation/HeaderManipulation.ino
index 4fe34dc..5b4c9f7 100644
--- a/examples/HeaderManipulation/HeaderManipulation.ino
+++ b/examples/HeaderManipulation/HeaderManipulation.ino
@@ -6,7 +6,7 @@
//
#include
-#ifdef ESP32
+#if defined(ESP32) || defined(LIBRETINY)
#include
#include
#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();
}
diff --git a/examples/Headers/Headers.ino b/examples/Headers/Headers.ino
index e07c515..eee87ac 100644
--- a/examples/Headers/Headers.ino
+++ b/examples/Headers/Headers.ino
@@ -6,7 +6,7 @@
//
#include
-#ifdef ESP32
+#if defined(ESP32) || defined(LIBRETINY)
#include
#include
#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
diff --git a/examples/Json/Json.ino b/examples/Json/Json.ino
index 0ea8892..a29fec1 100644
--- a/examples/Json/Json.ino
+++ b/examples/Json/Json.ino
@@ -6,7 +6,7 @@
//
#include
-#ifdef ESP32
+#if defined(ESP32) || defined(LIBRETINY)
#include
#include
#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();
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();
root["hello"] = json.as()["name"];
diff --git a/examples/Logging/Logging.ino b/examples/Logging/Logging.ino
index 6485185..ae504b2 100644
--- a/examples/Logging/Logging.ino
+++ b/examples/Logging/Logging.ino
@@ -6,7 +6,7 @@
//
#include
-#ifdef ESP32
+#if defined(ESP32) || defined(LIBRETINY)
#include
#include
#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
diff --git a/examples/MessagePack/MessagePack.ino b/examples/MessagePack/MessagePack.ino
index 4fea247..e038d03 100644
--- a/examples/MessagePack/MessagePack.ino
+++ b/examples/MessagePack/MessagePack.ino
@@ -6,7 +6,7 @@
//
#include
-#ifdef ESP32
+#if defined(ESP32) || defined(LIBRETINY)
#include
#include
#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
diff --git a/examples/Middleware/Middleware.ino b/examples/Middleware/Middleware.ino
index c52f949..992a0a2 100644
--- a/examples/Middleware/Middleware.ino
+++ b/examples/Middleware/Middleware.ino
@@ -6,7 +6,7 @@
//
#include
-#ifdef ESP32
+#if defined(ESP32) || defined(LIBRETINY)
#include
#include
#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
diff --git a/examples/Params/Params.ino b/examples/Params/Params.ino
index 2c438a5..416218c 100644
--- a/examples/Params/Params.ino
+++ b/examples/Params/Params.ino
@@ -6,7 +6,7 @@
//
#include
-#ifdef ESP32
+#if defined(ESP32) || defined(LIBRETINY)
#include
#include
#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
diff --git a/examples/PartitionDownloader/PartitionDownloader.ino b/examples/PartitionDownloader/PartitionDownloader.ino
index 3c76366..1174640 100644
--- a/examples/PartitionDownloader/PartitionDownloader.ino
+++ b/examples/PartitionDownloader/PartitionDownloader.ino
@@ -7,7 +7,7 @@
//
#include
-#ifdef ESP32
+#if defined(ESP32) || defined(LIBRETINY)
#include
#include
#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
diff --git a/examples/PerfTests/PerfTests.ino b/examples/PerfTests/PerfTests.ino
index 6467d2c..001512c 100644
--- a/examples/PerfTests/PerfTests.ino
+++ b/examples/PerfTests/PerfTests.ino
@@ -6,7 +6,7 @@
//
#include
-#ifdef ESP32
+#if defined(ESP32) || defined(LIBRETINY)
#include
#include
#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
diff --git a/examples/RateLimit/RateLimit.ino b/examples/RateLimit/RateLimit.ino
index 89d6090..5ae93e7 100644
--- a/examples/RateLimit/RateLimit.ino
+++ b/examples/RateLimit/RateLimit.ino
@@ -6,7 +6,7 @@
//
#include
-#ifdef ESP32
+#if defined(ESP32) || defined(LIBRETINY)
#include
#include
#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
diff --git a/examples/Redirect/Redirect.ino b/examples/Redirect/Redirect.ino
index ce1b9fb..8f10557 100644
--- a/examples/Redirect/Redirect.ino
+++ b/examples/Redirect/Redirect.ino
@@ -6,7 +6,7 @@
//
#include
-#ifdef ESP32
+#if defined(ESP32) || defined(LIBRETINY)
#include
#include
#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
diff --git a/examples/RequestContinuation/RequestContinuation.ino b/examples/RequestContinuation/RequestContinuation.ino
index 0584cf1..f59322e 100644
--- a/examples/RequestContinuation/RequestContinuation.ino
+++ b/examples/RequestContinuation/RequestContinuation.ino
@@ -6,7 +6,7 @@
//
#include
-#ifdef ESP32
+#if defined(ESP32) || defined(LIBRETINY)
#include
#include
#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
diff --git a/examples/RequestContinuationComplete/RequestContinuationComplete.ino b/examples/RequestContinuationComplete/RequestContinuationComplete.ino
index ccd16fd..cb4a53f 100644
--- a/examples/RequestContinuationComplete/RequestContinuationComplete.ino
+++ b/examples/RequestContinuationComplete/RequestContinuationComplete.ino
@@ -6,7 +6,7 @@
//
#include
-#ifdef ESP32
+#if defined(ESP32) || defined(LIBRETINY)
#include
#include
#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
diff --git a/examples/ResumableDownload/ResumableDownload.ino b/examples/ResumableDownload/ResumableDownload.ino
index 373ca24..68646e7 100644
--- a/examples/ResumableDownload/ResumableDownload.ino
+++ b/examples/ResumableDownload/ResumableDownload.ino
@@ -6,7 +6,7 @@
//
#include
-#ifdef ESP32
+#if defined(ESP32) || defined(LIBRETINY)
#include
#include
#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
diff --git a/examples/Rewrite/Rewrite.ino b/examples/Rewrite/Rewrite.ino
index 6981b11..8dfeedc 100644
--- a/examples/Rewrite/Rewrite.ino
+++ b/examples/Rewrite/Rewrite.ino
@@ -6,7 +6,7 @@
//
#include
-#ifdef ESP32
+#if defined(ESP32) || defined(LIBRETINY)
#include
#include
#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
diff --git a/examples/ServerSentEvents/ServerSentEvents.ino b/examples/ServerSentEvents/ServerSentEvents.ino
index 91e2c1d..5567ec6 100644
--- a/examples/ServerSentEvents/ServerSentEvents.ino
+++ b/examples/ServerSentEvents/ServerSentEvents.ino
@@ -6,7 +6,7 @@
//
#include
-#ifdef ESP32
+#if defined(ESP32) || defined(LIBRETINY)
#include
#include
#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
diff --git a/examples/ServerSentEvents_PR156/ServerSentEvents_PR156.ino b/examples/ServerSentEvents_PR156/ServerSentEvents_PR156.ino
new file mode 100644
index 0000000..cced715
--- /dev/null
+++ b/examples/ServerSentEvents_PR156/ServerSentEvents_PR156.ino
@@ -0,0 +1,141 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
+
+//
+// SSE example
+//
+
+#include
+#if defined(ESP32) || defined(LIBRETINY)
+#include
+#include
+#elif defined(ESP8266)
+#include
+#include
+#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
+#include
+#include
+#endif
+
+#include
+
+static const char *htmlContent PROGMEM = R"(
+
+
+
+ Server-Sent Events
+
+
+
+ Open your browser console!
+
+
+)";
+
+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
+}
diff --git a/examples/ServerState/ServerState.ino b/examples/ServerState/ServerState.ino
index 8501758..4ceddbc 100644
--- a/examples/ServerState/ServerState.ino
+++ b/examples/ServerState/ServerState.ino
@@ -6,7 +6,7 @@
//
#include
-#ifdef ESP32
+#if defined(ESP32) || defined(LIBRETINY)
#include
#include
#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
diff --git a/examples/SkipServerMiddleware/SkipServerMiddleware.ino b/examples/SkipServerMiddleware/SkipServerMiddleware.ino
index d232c71..0e7f172 100644
--- a/examples/SkipServerMiddleware/SkipServerMiddleware.ino
+++ b/examples/SkipServerMiddleware/SkipServerMiddleware.ino
@@ -6,7 +6,7 @@
//
#include
-#ifdef ESP32
+#if defined(ESP32) || defined(LIBRETINY)
#include
#include
#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
diff --git a/examples/SlowChunkResponse/SlowChunkResponse.ino b/examples/SlowChunkResponse/SlowChunkResponse.ino
index bbf70b6..7844ad6 100644
--- a/examples/SlowChunkResponse/SlowChunkResponse.ino
+++ b/examples/SlowChunkResponse/SlowChunkResponse.ino
@@ -7,7 +7,7 @@
//
#include
-#ifdef ESP32
+#if defined(ESP32) || defined(LIBRETINY)
#include
#include
#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
diff --git a/examples/StaticFile/StaticFile.ino b/examples/StaticFile/StaticFile.ino
index 331f287..edc2cb2 100644
--- a/examples/StaticFile/StaticFile.ino
+++ b/examples/StaticFile/StaticFile.ino
@@ -6,7 +6,7 @@
//
#include
-#ifdef ESP32
+#if defined(ESP32) || defined(LIBRETINY)
#include
#include
#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
diff --git a/examples/Templates/Templates.ino b/examples/Templates/Templates.ino
index edc02c2..fdf4eb6 100644
--- a/examples/Templates/Templates.ino
+++ b/examples/Templates/Templates.ino
@@ -6,7 +6,7 @@
//
#include
-#ifdef ESP32
+#if defined(ESP32) || defined(LIBRETINY)
#include
#include
#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
diff --git a/examples/Upload/Upload.ino b/examples/Upload/Upload.ino
index ceac47d..fd80bd7 100644
--- a/examples/Upload/Upload.ino
+++ b/examples/Upload/Upload.ino
@@ -6,7 +6,7 @@
//
#include
-#ifdef ESP32
+#if defined(ESP32) || defined(LIBRETINY)
#include
#include
#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
diff --git a/examples/WebSocket/WebSocket.ino b/examples/WebSocket/WebSocket.ino
index 9fb6ffd..c8d3727 100644
--- a/examples/WebSocket/WebSocket.ino
+++ b/examples/WebSocket/WebSocket.ino
@@ -6,7 +6,7 @@
//
#include
-#ifdef ESP32
+#if defined(ESP32) || defined(LIBRETINY)
#include
#include
#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());
diff --git a/examples/WebSocketEasy/WebSocketEasy.ino b/examples/WebSocketEasy/WebSocketEasy.ino
index 12b03ce..5229910 100644
--- a/examples/WebSocketEasy/WebSocketEasy.ino
+++ b/examples/WebSocketEasy/WebSocketEasy.ino
@@ -6,7 +6,7 @@
//
#include
-#ifdef ESP32
+#if defined(ESP32) || defined(LIBRETINY)
#include
#include
#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
diff --git a/idf_component.yml b/idf_component.yml
index 7969fda..d2afdf8 100644
--- a/idf_component.yml
+++ b/idf_component.yml
@@ -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
diff --git a/idf_component_examples/catchall/main/main.cpp b/idf_component_examples/catchall/main/main.cpp
index c491588..0ba61ec 100644
--- a/idf_component_examples/catchall/main/main.cpp
+++ b/idf_component_examples/catchall/main/main.cpp
@@ -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
diff --git a/idf_component_examples/serversentevents/main/main.cpp b/idf_component_examples/serversentevents/main/main.cpp
index 59a1f59..b877f54 100644
--- a/idf_component_examples/serversentevents/main/main.cpp
+++ b/idf_component_examples/serversentevents/main/main.cpp
@@ -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
diff --git a/idf_component_examples/websocket/main/main.cpp b/idf_component_examples/websocket/main/main.cpp
index 843d1a4..9d4e46a 100644
--- a/idf_component_examples/websocket/main/main.cpp
+++ b/idf_component_examples/websocket/main/main.cpp
@@ -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
diff --git a/library.json b/library.json_
similarity index 88%
rename from library.json
rename to library.json_
index bd9b884..4d13d28 100644
--- a/library.json
+++ b/library.json_
@@ -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",
diff --git a/library.properties b/library.properties
index 4a1b210..774972b 100644
--- a/library.properties
+++ b/library.properties
@@ -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
diff --git a/pioarduino_examples/IncreaseMaxSockets/platformio.ini b/pioarduino_examples/IncreaseMaxSockets/platformio.ini
index 08d7a50..e42d0a7 100644
--- a/pioarduino_examples/IncreaseMaxSockets/platformio.ini
+++ b/pioarduino_examples/IncreaseMaxSockets/platformio.ini
@@ -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
diff --git a/platformio.ini b/platformio.ini
index a0129d4..4695893 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -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
diff --git a/src/AsyncEventSource.cpp b/src/AsyncEventSource.cpp
index 797c5d1..2ebfa2d 100644
--- a/src/AsyncEventSource.cpp
+++ b/src/AsyncEventSource.cpp
@@ -193,7 +193,7 @@ AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest *request, A
AsyncEventSourceClient::~AsyncEventSourceClient() {
#ifdef ESP32
- std::lock_guard lock(_lockmq);
+ std::lock_guard 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 lock(_lockmq);
+ std::lock_guard 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 lock(_lockmq);
+ std::lock_guard 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 lock(_lockmq);
+ std::lock_guard 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 lock(_lockmq);
+ std::lock_guard lock(_lockmq);
#endif
_runQueue();
}
@@ -367,7 +367,7 @@ void AsyncEventSource::_addClient(AsyncEventSourceClient *client) {
return;
}
#ifdef ESP32
- std::lock_guard lock(_client_queue_lock);
+ std::lock_guard 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 lock(_client_queue_lock);
+ std::lock_guard 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 lock(_client_queue_lock);
+ std::lock_guard 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 lock(_client_queue_lock);
+ std::lock_guard 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(generateEventMessage(message, event, id, reconnect));
#ifdef ESP32
- std::lock_guard lock(_client_queue_lock);
+ std::lock_guard 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 lock(_client_queue_lock);
+ std::lock_guard lock(_client_queue_lock);
#endif
size_t n_clients{0};
for (const auto &i : _clients) {
diff --git a/src/AsyncEventSource.h b/src/AsyncEventSource.h
index fe4c840..ccfda95 100644
--- a/src/AsyncEventSource.h
+++ b/src/AsyncEventSource.h
@@ -6,8 +6,13 @@
#include
-#ifdef ESP32
+#if defined(ESP32) || defined(LIBRETINY)
#include
+#ifdef LIBRETINY
+#ifdef round
+#undef round
+#endif
+#endif
#include
#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 _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;
diff --git a/src/AsyncJson.cpp b/src/AsyncJson.cpp
index b8d014b..8381f75 100644
--- a/src/AsyncJson.cpp
+++ b/src/AsyncJson.cpp
@@ -113,53 +113,77 @@ 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));
- if (json.success()) {
+ DynamicJsonBuffer jsonBuffer;
+ 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));
- if (!error) {
- JsonVariant json = jsonBuffer.as();
+ DynamicJsonDocument jsonBuffer(this->maxJsonBufferSize);
+ DeserializationError error = deserializeJson(jsonBuffer, (const char *)request->_tempObject);
+ if (!error) {
+ JsonVariant json = jsonBuffer.as();
#else
- JsonDocument jsonBuffer;
- DeserializationError error = deserializeJson(jsonBuffer, (uint8_t *)(request->_tempObject));
- if (!error) {
- JsonVariant json = jsonBuffer.as();
+ JsonDocument jsonBuffer;
+ DeserializationError error = deserializeJson(jsonBuffer, (const char *)request->_tempObject);
+ if (!error) {
+ JsonVariant json = jsonBuffer.as();
#endif
- _onRequest(request, json);
- return;
- }
+ _onRequest(request, json);
+ } else {
+ // error parsing the body
+ request->send(400);
}
- request->send(_contentLength > _maxContentLength ? 413 : 400);
- } else {
- request->send(500);
}
}
void AsyncCallbackJsonWebHandler::handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
if (_onRequest) {
- _contentLength = total;
- if (total > 0 && request->_tempObject == NULL && total < _maxContentLength) {
- request->_tempObject = malloc(total);
+ // 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");
+ log_e("Failed to allocate");
#endif
- request->abort();
- return;
+ request->abort();
+ 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);
}
}
}
diff --git a/src/AsyncJson.h b/src/AsyncJson.h
index b12894b..2194069 100644
--- a/src/AsyncJson.h
+++ b/src/AsyncJson.h
@@ -4,8 +4,14 @@
#ifndef ASYNC_JSON_H_
#define ASYNC_JSON_H_
+#if __has_include("ArduinoJson.h")
#include
+#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
@@ -73,7 +79,6 @@ protected:
String _uri;
WebRequestMethodComposite _method;
ArJsonRequestHandlerFunction _onRequest;
- size_t _contentLength;
#if ARDUINOJSON_VERSION_MAJOR == 6
size_t maxJsonBufferSize;
#endif
diff --git a/src/AsyncWebHeader.cpp b/src/AsyncWebHeader.cpp
index 6d82f74..fdeb74a 100644
--- a/src/AsyncWebHeader.cpp
+++ b/src/AsyncWebHeader.cpp
@@ -3,30 +3,32 @@
#include
-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));
}
diff --git a/src/AsyncWebServerRequest.cpp b/src/AsyncWebServerRequest.cpp
new file mode 100644
index 0000000..18b4da0
--- /dev/null
+++ b/src/AsyncWebServerRequest.cpp
@@ -0,0 +1,85 @@
+#include
+
+/**
+ * @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';
+}
diff --git a/src/AsyncWebServerVersion.h b/src/AsyncWebServerVersion.h
index 4f3fdc4..3a7a34c 100644
--- a/src/AsyncWebServerVersion.h
+++ b/src/AsyncWebServerVersion.h
@@ -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
diff --git a/src/AsyncWebSocket.cpp b/src/AsyncWebSocket.cpp
index 3f91ae7..0cd1749 100644
--- a/src/AsyncWebSocket.cpp
+++ b/src/AsyncWebSocket.cpp
@@ -17,6 +17,8 @@
#include
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) || defined(ESP8266)
#include
+#elif defined(LIBRETINY)
+#include
#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 lock(_lock);
+ std::lock_guard 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 lock(_lock);
+ std::unique_lock 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 lock(_lock);
+ std::unique_lock 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 lock(_lock);
+ std::lock_guard lock(_lock);
#endif
return (_messageQueue.size() >= WS_MAX_QUEUED_MESSAGES) || (_status != WS_CONNECTED);
}
size_t AsyncWebSocketClient::queueLen() const {
#ifdef ESP32
- std::lock_guard lock(_lock);
+ std::lock_guard lock(_lock);
#endif
return _messageQueue.size();
}
bool AsyncWebSocketClient::canSend() const {
#ifdef ESP32
- std::lock_guard lock(_lock);
+ std::lock_guard 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 lock(_lock);
+ std::lock_guard 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 lock(_lock);
+ std::unique_lock 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);
diff --git a/src/AsyncWebSocket.h b/src/AsyncWebSocket.h
index 2318834..46cdb5e 100644
--- a/src/AsyncWebSocket.h
+++ b/src/AsyncWebSocket.h
@@ -5,8 +5,14 @@
#define ASYNCWEBSOCKET_H_
#include
-#ifdef ESP32
+
+#if defined(ESP32) || defined(LIBRETINY)
#include
+#ifdef LIBRETINY
+#ifdef round
+#undef round
+#endif
+#endif
#include
#ifndef WS_MAX_QUEUED_MESSAGES
#define WS_MAX_QUEUED_MESSAGES 32
@@ -47,7 +53,62 @@ using AsyncWebSocketSharedBuffer = std::shared_ptr>;
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 _controlQueue;
std::deque _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;
diff --git a/src/ESPAsyncWebServer.h b/src/ESPAsyncWebServer.h
index 24233cd..0cf1323 100644
--- a/src/ESPAsyncWebServer.h
+++ b/src/ESPAsyncWebServer.h
@@ -15,16 +15,13 @@
#include
#include
-#ifdef ESP32
+#if defined(ESP32) || defined(LIBRETINY)
#include
-#include
#elif defined(ESP8266)
-#include
#include
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include
#include
-#include
#include
#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);
diff --git a/src/Middleware.cpp b/src/Middleware.cpp
index 890303d..5e9c3c2 100644
--- a/src/Middleware.cpp
+++ b/src/Middleware.cpp
@@ -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('>');
diff --git a/src/WebHandlers.cpp b/src/WebHandlers.cpp
index acfc7c0..21dba00 100644
--- a/src/WebHandlers.cpp
+++ b/src/WebHandlers.cpp
@@ -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();
diff --git a/src/WebRequest.cpp b/src/WebRequest.cpp
index 8b735af..ff4cf4c 100644
--- a/src/WebRequest.cpp
+++ b/src/WebRequest.cpp
@@ -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
diff --git a/src/WebResponseImpl.h b/src/WebResponseImpl.h
index 6408625..2b9318a 100644
--- a/src/WebResponseImpl.h
+++ b/src/WebResponseImpl.h
@@ -75,7 +75,6 @@ class AsyncFileResponse : public AsyncAbstractResponse {
private:
File _content;
- String _path;
void _setContentTypeFromPath(const String &path);
public:
diff --git a/src/WebResponses.cpp b/src/WebResponses.cpp
index 97981bd..2878a2c 100644
--- a/src/WebResponses.cpp
+++ b/src/WebResponses.cpp
@@ -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(ptr);
- while (count--) {
- if (*p++ == static_cast(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;
- addHeader(T_Content_Encoding, T_gzip, false);
- _callback = nullptr; // Unable to process zipped templates
- _sendContentLength = true;
- _chunked = false;
+ // 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;
}
- 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);
+ // 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;
+ snprintf(buf, sizeof(buf), T_attachment, filename);
+ addHeader(T_Content_Disposition, buf, false);
} else {
- // set filename and force rendering
- snprintf_P(buf, sizeof(buf), PSTR("inline"));
+ // Serve file inline (display in browser)
+ addHeader(T_Content_Disposition, T_inline, false);
}
- addHeader(T_Content_Disposition, buf, 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(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;
diff --git a/src/WebServer.cpp b/src/WebServer.cpp
index 7fc54bf..c3c3ee7 100644
--- a/src/WebServer.cpp
+++ b/src/WebServer.cpp
@@ -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
+#elif defined(ESP8266)
+#include
+#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;
diff --git a/src/literals.h b/src/literals.h
index a69f78b..fc0fff1 100644
--- a/src/literals.h
+++ b/src/literals.h
@@ -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