3 Commits

153 changed files with 5819 additions and 21277 deletions

View File

@@ -1,246 +0,0 @@
# 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

View File

@@ -1,8 +0,0 @@
[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 =

View File

@@ -1,60 +0,0 @@
# 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

View File

@@ -1,2 +0,0 @@
# https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/configuring-issue-templates-for-your-repository
blank_issues_enabled: false

View File

@@ -1,91 +0,0 @@
name: 🐛 Bug Report
description: File a bug report
labels: ["Status: Awaiting triage"]
body:
- type: markdown
attributes:
value: >
### ⚠️ Please remember: issues are for *bugs* only!
- type: markdown
attributes:
value: |
#### Unsure? Have a questions? 👉 [Start a new discussion](https://github.com/ESP32Async/ESPAsyncWebServer/discussions/new)
#### Before opening a new issue, please make sure you have searched:
- In the [documentation](https://github.com/ESP32Async/ESPAsyncWebServer)
- In the [discussions](https://github.com/ESP32Async/ESPAsyncWebServer/discussions)
- In the [issues](https://github.com/ESP32Async/ESPAsyncWebServer/issues)
- In the [examples](https://github.com/ESP32Async/ESPAsyncWebServer/tree/main/examples)
#### Make sure you are using:
- The [latest version of ESPAsyncWebServer](https://github.com/ESP32Async/ESPAsyncWebServer/releases)
- The [latest version of AsyncTCP](https://github.com/ESP32Async/AsyncTCP/releases) (for ESP32)
- type: dropdown
id: platform
attributes:
label: Platform
options:
- ESP32
- ESP8266
- RP2040
validations:
required: true
- type: dropdown
id: tooling
attributes:
label: IDE / Tooling
options:
- Arduino (IDE/CLI)
- pioarduino
- ESP-IDF
- PlatformIO
validations:
required: true
- type: textarea
id: what-happened
attributes:
label: What happened?
description: A clear and concise description of the issue.
placeholder: A clear and concise description of the issue.
validations:
required: true
- type: textarea
id: stack-trace
attributes:
label: Stack Trace
description: Please provide a debug message or error message. If you have a Guru Meditation Error or Backtrace, [please decode it](https://maximeborges.github.io/esp-stacktrace-decoder/).
placeholder: For Arduino IDE, Enable Core debug level - Debug on tools menu, then put the serial output here.
validations:
required: true
- type: textarea
id: how-to-reproduce
attributes:
label: Minimal Reproductible Example (MRE)
description: Post the code or the steps to reproduce the issue.
placeholder: Post the code or the steps to reproduce the issue.
validations:
required: true
- type: checkboxes
id: confirmation
attributes:
label: "I confirm that:"
options:
- label: I have read the documentation.
required: true
- label: I have searched for similar discussions.
required: true
- label: I have searched for similar issues.
required: true
- label: I have looked at the examples.
required: true
- label: I have upgraded to the lasted version of ESPAsyncWebServer (and AsyncTCP for ESP32).
required: true

View File

@@ -1,10 +0,0 @@
# Set update schedule for GitHub Actions
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
# Check for updates to GitHub Actions every week
interval: "weekly"

View File

@@ -1,40 +0,0 @@
#!/bin/bash
# shellcheck disable=SC2002
# fail the script if any command unexpectedly fails
set -e
if [ ! $# -eq 3 ]; then
echo "Bad number of arguments: $#" >&2
echo "usage: $0 <major> <minor> <patch>" >&2
exit 1
fi
re='^[0-9]+$'
if [[ ! $1 =~ $re ]] || [[ ! $2 =~ $re ]] || [[ ! $3 =~ $re ]] ; then
echo "error: Not a valid version: $1.$2.$3" >&2
echo "usage: $0 <major> <minor> <patch>" >&2
exit 1
fi
ASYNCWEBSERVER_VERSION_MAJOR="$1"
ASYNCWEBSERVER_VERSION_MINOR="$2"
ASYNCWEBSERVER_VERSION_PATCH="$3"
ASYNCWEBSERVER_VERSION="$ASYNCWEBSERVER_VERSION_MAJOR.$ASYNCWEBSERVER_VERSION_MINOR.$ASYNCWEBSERVER_VERSION_PATCH"
echo "New AsyncTCP version: $ASYNCWEBSERVER_VERSION"
echo "Updating library.properties..."
cat library.properties | sed "s/version=.*/version=$ASYNCWEBSERVER_VERSION/g" > __library.properties && mv __library.properties library.properties
echo "Updating library.json..."
cat library.json | sed "s/^ \"version\":.*/ \"version\": \"$ASYNCWEBSERVER_VERSION\",/g" > __library.json && mv __library.json library.json
echo "Updating src/AsyncWebServerVersion.h..."
cat src/AsyncWebServerVersion.h | \
sed "s/#define ASYNCWEBSERVER_VERSION_MAJOR.*/#define ASYNCWEBSERVER_VERSION_MAJOR $ASYNCWEBSERVER_VERSION_MAJOR/g" | \
sed "s/#define ASYNCWEBSERVER_VERSION_MINOR.*/#define ASYNCWEBSERVER_VERSION_MINOR $ASYNCWEBSERVER_VERSION_MINOR/g" | \
sed "s/#define ASYNCWEBSERVER_VERSION_PATCH.*/#define ASYNCWEBSERVER_VERSION_PATCH $ASYNCWEBSERVER_VERSION_PATCH/g" > src/__AsyncWebServerVersion.h && mv src/__AsyncWebServerVersion.h src/AsyncWebServerVersion.h
exit 0

View File

@@ -1,33 +0,0 @@
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
name: Arduino Lint
on:
workflow_dispatch:
push:
branches:
- main
- release/*
paths-ignore:
- "docs/**"
- "mkdocs.yml"
- "README.md"
pull_request:
paths-ignore:
- "docs/**"
- "mkdocs.yml"
- "README.md"
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
arduino-lint:
name: Arduino Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: arduino/arduino-lint-action@v2
with:
library-manager: update

View File

@@ -1,202 +0,0 @@
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
name: Build (ESP32)
on:
workflow_dispatch:
push:
branches:
- main
- release/*
paths-ignore:
- "docs/**"
- "mkdocs.yml"
- "README.md"
pull_request:
paths-ignore:
- "docs/**"
- "mkdocs.yml"
- "README.md"
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
arduino-esp32:
name: ESP32 (arduino-cli) - Release
runs-on: ubuntu-latest
steps:
- name: Install arduino-cli
run: curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | BINDIR=/usr/local/bin sh
- name: Update core index
run: arduino-cli core update-index --additional-urls https://espressif.github.io/arduino-esp32/package_esp32_index.json
- name: Install core
run: arduino-cli core install --additional-urls https://espressif.github.io/arduino-esp32/package_esp32_index.json esp32:esp32
- name: Install ArduinoJson
run: arduino-cli lib install ArduinoJson
- name: Install AsyncTCP (ESP32)
run: ARDUINO_LIBRARY_ENABLE_UNSAFE_INSTALL=true arduino-cli lib install --git-url https://github.com/ESP32Async/AsyncTCP#v3.4.10
- name: Checkout
uses: actions/checkout@v5
- name: Build Examples
run: |
for i in `ls examples`; do
echo "============================================================="
echo "Building examples/$i..."
echo "============================================================="
arduino-cli compile --library . --warnings none -b esp32:esp32:esp32 "examples/$i/$i.ino"
done
arduino-esp32-dev:
name: ESP32 (arduino-cli) - Dev
runs-on: ubuntu-latest
steps:
- name: Install arduino-cli
run: curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | BINDIR=/usr/local/bin sh
- name: Update core index
run: arduino-cli core update-index --additional-urls https://espressif.github.io/arduino-esp32/package_esp32_dev_index.json
- name: Install core
run: arduino-cli core install --additional-urls https://espressif.github.io/arduino-esp32/package_esp32_dev_index.json esp32:esp32
- name: Install ArduinoJson
run: arduino-cli lib install ArduinoJson
- name: Install AsyncTCP (ESP32)
run: ARDUINO_LIBRARY_ENABLE_UNSAFE_INSTALL=true arduino-cli lib install --git-url https://github.com/ESP32Async/AsyncTCP#v3.4.10
- name: Checkout
uses: actions/checkout@v5
- name: Build Examples
run: |
for i in `ls examples`; do
echo "============================================================="
echo "Building examples/$i..."
echo "============================================================="
arduino-cli compile --library . --warnings none -b esp32:esp32:esp32 "examples/$i/$i.ino"
done
platformio-esp32-arduino2:
name: ESP32 (pio) - Arduino 2
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
board:
- esp32dev
- esp32-s2-saola-1
- esp32-s3-devkitc-1
- esp32-c3-devkitc-02
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Python
uses: actions/setup-python@v6
with:
python-version: "3.13"
- name: Install uv
uses: astral-sh/setup-uv@v6
with:
version: "latest"
enable-cache: false
- name: Install platformio
run: |
uv pip install --system -U https://github.com/pioarduino/platformio-core/archive/refs/tags/v6.1.18.zip
- name: Build Examples
run: |
for i in `ls examples`; do
echo "============================================================="
echo "Building examples/$i..."
echo "============================================================="
PLATFORMIO_SRC_DIR=examples/$i PIO_BOARD=${{ matrix.board }} pio run -e ci-arduino-2
done
platformio-esp32-arduino-3:
name: ESP32 (pio) - Arduino 3
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
board:
- esp32dev
- esp32-s2-saola-1
- esp32-s3-devkitc-1
- esp32-c3-devkitc-02
- esp32-c6-devkitc-1
- esp32-h2-devkitm-1
- esp32-p4
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Python
uses: actions/setup-python@v6
with:
python-version: "3.13"
- name: Install uv
uses: astral-sh/setup-uv@v6
with:
version: "latest"
enable-cache: false
- name: Install platformio
run: |
uv pip install --system -U https://github.com/pioarduino/platformio-core/archive/refs/tags/v6.1.18.zip
- name: Build Examples
run: |
for i in `ls examples`; do
echo "============================================================="
echo "Building examples/$i..."
echo "============================================================="
PLATFORMIO_SRC_DIR=examples/$i PIO_BOARD=${{ matrix.board }} pio run -e ci-arduino-3
done
platformio-specific-envs:
name: ESP32 (pio) - Specific Envs
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
env:
- ci-latest-asynctcp
- ci-no-json
- ci-no-chunk-inflight
- ci-arduino-2-esp-idf-log
- ci-arduino-3-esp-idf-log
- ci-regex
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Python
uses: actions/setup-python@v6
with:
python-version: "3.13"
- name: Install uv
uses: astral-sh/setup-uv@v6
with:
version: "latest"
enable-cache: false
- name: Install platformio
run: |
uv pip install --system -U https://github.com/pioarduino/platformio-core/archive/refs/tags/v6.1.18.zip
- name: Build Examples
run: |
for i in `ls examples`; do
echo "============================================================="
echo "Building examples/$i..."
echo "============================================================="
PLATFORMIO_SRC_DIR=examples/$i PIO_BOARD=esp32dev pio run -e ${{ matrix.env }}
done

View File

@@ -1,90 +0,0 @@
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
name: Build (8266)
on:
workflow_dispatch:
push:
branches:
- main
- release/*
paths-ignore:
- "docs/**"
- "mkdocs.yml"
- "README.md"
pull_request:
paths-ignore:
- "docs/**"
- "mkdocs.yml"
- "README.md"
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
arduino-esp8266:
name: ESP8266 (arduino-cli)
runs-on: ubuntu-latest
steps:
- name: Install arduino-cli
run: curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | BINDIR=/usr/local/bin sh
- name: Update core index
run: arduino-cli core update-index --additional-urls https://arduino.esp8266.com/stable/package_esp8266com_index.json
- name: Install core
run: arduino-cli core install --additional-urls https://arduino.esp8266.com/stable/package_esp8266com_index.json esp8266:esp8266
- name: Install ArduinoJson
run: arduino-cli lib install ArduinoJson
- name: Install ESPAsyncTCP (ESP8266)
run: ARDUINO_LIBRARY_ENABLE_UNSAFE_INSTALL=true arduino-cli lib install --git-url https://github.com/ESP32Async/ESPAsyncTCP#v2.0.0
- name: Checkout
uses: actions/checkout@v5
- name: Build Examples
run: |
for i in `ls examples`; do
echo "============================================================="
echo "Building examples/$i..."
echo "============================================================="
arduino-cli compile --library . --warnings none -b esp8266:esp8266:huzzah "examples/$i/$i.ino"
done
platformio-esp8266:
name: ESP8266 (pio)
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
board:
- huzzah
- d1_mini
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Python
uses: actions/setup-python@v6
with:
python-version: "3.13"
- name: Install uv
uses: astral-sh/setup-uv@v6
with:
version: "latest"
enable-cache: false
- name: Install platformio
run: |
uv pip install --system -U https://github.com/pioarduino/platformio-core/archive/refs/tags/v6.1.18.zip
- name: Build Examples
run: |
for i in `ls examples`; do
echo "============================================================="
echo "Building examples/$i..."
echo "============================================================="
PLATFORMIO_SRC_DIR=examples/$i PIO_BOARD=${{ matrix.board }} pio run -e ci-esp8266
done

View File

@@ -1,59 +0,0 @@
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
name: Build (LibreTiny)
on:
workflow_dispatch:
push:
branches:
- main
- release/*
paths-ignore:
- "docs/**"
- "mkdocs.yml"
- "README.md"
pull_request:
paths-ignore:
- "docs/**"
- "mkdocs.yml"
- "README.md"
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
platformio-libretiny:
name: LibreTiny (pio)
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
board:
- generic-bk7231n-qfn32-tuya
- generic-rtl8710bn-2mb-788k
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Python
uses: actions/setup-python@v6
with:
python-version: "3.13"
- name: Install uv
uses: astral-sh/setup-uv@v6
with:
version: "latest"
enable-cache: false
- name: Install platformio
run: |
uv pip install --system -U https://github.com/pioarduino/platformio-core/archive/refs/tags/v6.1.18.zip
- name: Build Examples
run: |
for i in AsyncResponseStream Auth Headers; do
echo "============================================================="
echo "Building examples/$i..."
echo "============================================================="
PLATFORMIO_SRC_DIR=examples/$i PIO_BOARD=${{ matrix.board }} pio run -e ci-libretiny
done

View File

@@ -1,97 +0,0 @@
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
name: Build (RPI)
on:
workflow_dispatch:
push:
branches:
- main
- release/*
paths-ignore:
- "docs/**"
- "mkdocs.yml"
- "README.md"
pull_request:
paths-ignore:
- "docs/**"
- "mkdocs.yml"
- "README.md"
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
arduino-rpi:
name: RPI (arduino-cli)
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
board:
- rpipicow
- rpipico2w
steps:
- name: Install arduino-cli
run: curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | BINDIR=/usr/local/bin sh
- name: Update core index
run: arduino-cli core update-index --additional-urls https://github.com/earlephilhower/arduino-pico/releases/download/4.4.4/package_rp2040_index.json
- name: Install core
run: arduino-cli core install --additional-urls https://github.com/earlephilhower/arduino-pico/releases/download/4.4.4/package_rp2040_index.json rp2040:rp2040
- name: Install ArduinoJson
run: arduino-cli lib install ArduinoJson
- name: Install RPAsyncTCP
run: ARDUINO_LIBRARY_ENABLE_UNSAFE_INSTALL=true arduino-cli lib install --git-url https://github.com/ayushsharma82/RPAsyncTCP#v1.3.2
- name: Checkout
uses: actions/checkout@v5
- name: Build Examples
run: |
for i in `ls examples`; do
echo "============================================================="
echo "Building examples/$i..."
echo "============================================================="
arduino-cli compile --library . --warnings none -b rp2040:rp2040:${{ matrix.board }} "examples/$i/$i.ino"
done
platformio-rpi:
name: RPI (pio)
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
board:
- rpipicow
- rpipico2w
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Python
uses: actions/setup-python@v6
with:
python-version: "3.13"
- name: Install uv
uses: astral-sh/setup-uv@v6
with:
version: "latest"
enable-cache: false
- name: Install platformio
run: |
uv pip install --system -U https://github.com/pioarduino/platformio-core/archive/refs/tags/v6.1.18.zip
- name: Build Examples
run: |
for i in `ls examples`; do
echo "============================================================="
echo "Building examples/$i..."
echo "============================================================="
PLATFORMIO_SRC_DIR=examples/$i PIO_BOARD=${{ matrix.board }} pio run -e ci-raspberrypi
done

View File

@@ -1,54 +0,0 @@
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
name: Cpplint
on:
workflow_dispatch:
push:
branches:
- main
- release/*
paths-ignore:
- "docs/**"
- "mkdocs.yml"
- "README.md"
pull_request:
paths-ignore:
- "docs/**"
- "mkdocs.yml"
- "README.md"
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
cpplint:
name: cpplint
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Cache
uses: actions/cache@v4
with:
key: ${{ runner.os }}-cpplint
path: ~/.cache/pip
- name: Pyhton
uses: actions/setup-python@v6
with:
python-version: "3.13"
- name: cpplint
run: |
python -m pip install --upgrade pip
pip install --upgrade cpplint
cpplint \
--repository=. \
--recursive \
--filter=-build/c++11,-build/namespaces,-readability/braces,-readability/casting,-readability/todo,-runtime/explicit,-runtime/indentation_namespace,-runtime/int,-runtime/references,-whitespace/blank_line,,-whitespace/braces,-whitespace/comments,-whitespace/indent,-whitespace/line_length,-whitespace/newline,-whitespace/parens \
lib \
include \
src

View File

@@ -1,64 +0,0 @@
# This needs to be in a separate workflow because it requires higher permissions than the calling workflow
name: Report Pre-commit Check Status
on:
workflow_run:
workflows: [Pre-commit hooks]
types:
- completed
permissions:
statuses: write
jobs:
report-success:
name: Report pre-commit success
if: github.event.workflow_run.conclusion == 'success'
runs-on: ubuntu-latest
steps:
- name: Report success
uses: actions/github-script@v7
with:
script: |
const owner = '${{ github.repository_owner }}';
const repo = '${{ github.repository }}'.split('/')[1];
const sha = '${{ github.event.workflow_run.head_sha }}';
core.debug(`owner: ${owner}`);
core.debug(`repo: ${repo}`);
core.debug(`sha: ${sha}`);
const { context: name, state } = (await github.rest.repos.createCommitStatus({
context: 'Pre-commit checks',
description: 'Pre-commit checks successful',
owner: owner,
repo: repo,
sha: sha,
state: 'success',
target_url: 'https://github.com/${{ github.repository }}/actions/runs/${{ github.event.workflow_run.id }}'
})).data;
core.info(`${name} is ${state}`);
report-pending:
name: Report pre-commit pending
if: github.event.workflow_run.conclusion != 'success'
runs-on: ubuntu-latest
steps:
- name: Report pending
uses: actions/github-script@v7
with:
script: |
const owner = '${{ github.repository_owner }}';
const repo = '${{ github.repository }}'.split('/')[1];
const sha = '${{ github.event.workflow_run.head_sha }}';
core.debug(`owner: ${owner}`);
core.debug(`repo: ${repo}`);
core.debug(`sha: ${sha}`);
const { context: name, state } = (await github.rest.repos.createCommitStatus({
context: 'Pre-commit checks',
description: 'The pre-commit checks need to be successful before merging',
owner: owner,
repo: repo,
sha: sha,
state: 'pending',
target_url: 'https://github.com/${{ github.repository }}/actions/runs/${{ github.event.workflow_run.id }}'
})).data;
core.info(`${name} is ${state}`);

View File

@@ -1,80 +0,0 @@
name: Pre-commit hooks
on:
workflow_dispatch:
push:
branches:
- main
pull_request:
types: [opened, reopened, synchronize, labeled]
concurrency:
group: pre-commit-${{github.event.pull_request.number || github.ref}}
cancel-in-progress: true
jobs:
lint:
if: |
github.event_name != 'pull_request' ||
contains(github.event.pull_request.labels.*.name, 'Status: Pending Merge') ||
contains(github.event.pull_request.labels.*.name, 'Re-trigger Pre-commit')
name: Check if fixes are needed
runs-on: ubuntu-latest
steps:
- name: Checkout latest commit
uses: actions/checkout@v5
with:
fetch-depth: 2
- name: Remove Label
if: contains(github.event.pull_request.labels.*.name, 'Re-trigger Pre-commit')
run: gh pr edit ${{ github.event.number }} --remove-label 'Re-trigger Pre-commit'
env:
GH_TOKEN: ${{ github.token }}
- name: Set up Python 3
uses: actions/setup-python@v6
with:
cache-dependency-path: pre-commit.requirements.txt
cache: "pip"
python-version: "3.13"
- name: Get Python version hash
run: |
echo "Using $(python -VV)"
echo "PY_HASH=$(python -VV | sha256sum | cut -d' ' -f1)" >> $GITHUB_ENV
- name: Restore pre-commit cache
uses: actions/cache/restore@v4
id: restore-cache
with:
path: |
~/.cache/pre-commit
key: pre-commit-${{ env.PY_HASH }}-${{ hashFiles('.pre-commit-config.yaml', '.github/workflows/pre-commit.yml', 'pre-commit.requirements.txt') }}
- name: Install python dependencies
run: python -m pip install -r pre-commit.requirements.txt
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@v42.0.2
- name: Run pre-commit hooks in changed files
run: pre-commit run --color=always --show-diff-on-failure --files ${{ steps.changed-files.outputs.all_changed_files }}
- name: Save pre-commit cache
uses: actions/cache/save@v4
if: ${{ always() && steps.restore-cache.outputs.cache-hit != 'true' }}
continue-on-error: true
with:
path: |
~/.cache/pre-commit
key: ${{ steps.restore-cache.outputs.cache-primary-key }}
- name: Push changes using pre-commit-ci-lite
uses: pre-commit-ci/lite-action@v1.1.0
# Only push changes in PRs
if: ${{ always() && github.event_name == 'pull_request' }}
with:
msg: "ci(pre-commit): Apply automatic fixes"

View File

@@ -1,62 +0,0 @@
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
name: Publish to PlatformIO
on:
workflow_dispatch:
inputs:
tag_name:
description: "Tag name of the release to publish (use 'latest' for the most recent tag)"
required: true
default: "latest"
jobs:
publish:
name: Publish
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v5
with:
fetch-tags: true
fetch-depth: 0 # Ensure all commits and tags are fetched
- name: Show latest tag
run: git tag --sort=-creatordate | head -n 1
- name: Download release zip
run: |
if [ "${{ inputs.tag_name }}" == "latest" ]; then
TAG_NAME=$(git tag --sort=-creatordate | head -n 1)
if [ -z "$TAG_NAME" ]; then
echo "Error: No tags found in the repository."
exit 1
fi
else
TAG_NAME="${{ inputs.tag_name }}"
fi
echo "Downloading tag: $TAG_NAME"
curl -L -o project.zip "https://github.com/${{ github.repository }}/archive/refs/tags/$TAG_NAME.zip"
# - name: Cache PlatformIO
# uses: actions/cache@v4
# with:
# key: ${{ runner.os }}-pio
# path: |
# ~/.cache/pip
# ~/.platformio
- name: Python
uses: actions/setup-python@v6
with:
python-version: "3.13"
- name: Install PlatformIO CLI
run: |
python -m pip install --upgrade pip
pip install --upgrade platformio
- name: Publish to PlatformIO
run: pio pkg publish --no-interactive --owner ${{ github.repository_owner }} project.zip
env:
PLATFORMIO_AUTH_TOKEN: ${{ secrets.PLATFORMIO_AUTH_TOKEN }}

View File

@@ -1,24 +0,0 @@
name: "Close stale issues and PRs"
on:
schedule:
- cron: "30 1 * * *"
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
with:
stale-issue-message: "This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 7 days."
stale-pr-message: "This PR is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 7 days."
close-issue-message: "This issue was closed because it has been stalled for 7 days with no activity."
close-pr-message: "This PR was closed because it has been stalled for 7 days with no activity."
days-before-issue-stale: 30
days-before-pr-stale: 30
days-before-issue-close: 7
days-before-pr-close: 7
permissions:
# contents: write # only for delete-branch option
issues: write
pull-requests: write

View File

@@ -1,50 +0,0 @@
name: Publish ESP-IDF Component
on:
workflow_dispatch:
inputs:
tag:
description: 'Component version (1.2.3, 1.2.3-rc1 or 1.2.3.4)'
required: true
git_ref:
description: 'Git ref with the source (branch, tag or commit)'
required: true
permissions:
contents: read
jobs:
upload_components:
runs-on: ubuntu-latest
steps:
- name: Get the release tag
env:
head_branch: ${{ inputs.tag || github.event.workflow_run.head_branch }}
run: |
# Read and sanitize the branch/tag name
branch=$(echo "$head_branch" | tr -cd '[:alnum:]/_.-')
if [[ $branch == refs/tags/* ]]; then
tag="${branch#refs/tags/}"
elif [[ $branch =~ ^[v]*[0-9]+\.[0-9]+\.[0-9]+.*$ ]]; then
tag=$branch
else
echo "Tag not found in $branch. Exiting..."
exit 1
fi
echo "Tag: $tag"
echo "RELEASE_TAG=$tag" >> $GITHUB_ENV
- uses: actions/checkout@v5
with:
ref: ${{ inputs.git_ref || env.RELEASE_TAG }}
submodules: "recursive"
- name: Upload components to the component registry
uses: espressif/upload-components-ci-action@v1
with:
name: espasyncwebserver
version: ${{ env.RELEASE_TAG }}
namespace: esp32async
api_token: ${{ secrets.IDF_COMPONENT_API_TOKEN }}

View File

@@ -1,20 +0,0 @@
name: Publish Website
on:
push:
branches:
- main
paths:
- "docs/**"
- "mkdocs.yml"
permissions:
contents: write
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: 3.x
- run: pip install mkdocs-material
- run: mkdocs gh-deploy --force

7
.gitignore vendored
View File

@@ -1,7 +0,0 @@
.DS_Store
.lh
/.pio
/.vscode
/logs
/site
/venv

2
.gitpod.Dockerfile vendored
View File

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

View File

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

View File

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

View File

@@ -6,4 +6,12 @@ set(COMPONENT_ADD_INCLUDEDIRS
"src"
)
set(COMPONENT_REQUIRES
"arduino-esp32"
"AsyncTCP"
)
register_component()
target_compile_definitions(${COMPONENT_TARGET} PUBLIC -DESP32)
target_compile_options(${COMPONENT_TARGET} PRIVATE -fno-rtti)

View File

@@ -1,129 +0,0 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socioeconomic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
https://sidweb.nl/cms3/en/contact.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

165
LICENSE
View File

@@ -1,165 +0,0 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.

1429
README.md

File diff suppressed because it is too large Load Diff

1
_config.yml Normal file
View File

@@ -0,0 +1 @@
theme: jekyll-theme-cayman

3
component.mk Normal file
View File

@@ -0,0 +1,3 @@
COMPONENT_ADD_INCLUDEDIRS := src
COMPONENT_SRCDIRS := src
CXXFLAGS += -fno-rtti

View File

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

View File

@@ -1,154 +0,0 @@
![https://avatars.githubusercontent.com/u/195753706?s=96&v=4](https://avatars.githubusercontent.com/u/195753706?s=96&v=4)
# ESPAsyncWebServer
[![Latest Release](https://img.shields.io/github/release/ESP32Async/ESPAsyncWebServer.svg)](https://GitHub.com/ESP32Async/ESPAsyncWebServer/releases/)
[![PlatformIO Registry](https://badges.registry.platformio.org/packages/ESP32Async/library/ESPAsyncWebServer.svg)](https://registry.platformio.org/libraries/ESP32Async/ESPAsyncWebServer)
[![License: LGPL 3.0](https://img.shields.io/badge/License-LGPL%203.0-yellow.svg)](https://opensource.org/license/lgpl-3-0/)
[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](code_of_conduct.md)
[![GitHub latest commit](https://badgen.net/github/last-commit/ESP32Async/ESPAsyncWebServer)](https://GitHub.com/ESP32Async/ESPAsyncWebServer/commit/)
[![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/ESP32Async/ESPAsyncWebServer)
[![ESP32Async Discord Server](https://img.shields.io/badge/Discord-ESP32Async-blue?logo=discord)](https://discord.gg/X7zpGdyUcY)
[![Documentation](https://img.shields.io/badge/Wiki-ESPAsyncWebServer-blue?logo=github)](https://github.com/ESP32Async/ESPAsyncWebServer/wiki)
## Asynchronous HTTP and WebSocket Server Library for ESP32, ESP8266, RP2040 and RP2350
Supports: WebSocket, SSE, Authentication, Arduino Json 7, File Upload, Static File serving, URL Rewrite, URL Redirect, etc.
- [Documentation](#documentation)
- [How to install](#how-to-install)
- [Dependencies](#dependencies)
- [ESP32 / pioarduino](#esp32--pioarduino)
- [ESP8266 / pioarduino](#esp8266--pioarduino)
- [Unofficial dependencies](#unofficial-dependencies)
## Documentation
The complete [project documentation](https://github.com/ESP32Async/ESPAsyncWebServer/wiki) is available in the Wiki section.
## How to install
The library can be downloaded from the releases page at [https://github.com/ESP32Async/ESPAsyncWebServer/releases](https://github.com/ESP32Async/ESPAsyncWebServer/releases).
It is also deployed in these registries:
- Arduino Library Registry: [https://github.com/arduino/library-registry](https://github.com/arduino/library-registry)
- ESP Component Registry [https://components.espressif.com/components/esp32async/espasyncwebserver](https://components.espressif.com/components/esp32async/espasyncwebserver)
- PlatformIO Registry: [https://registry.platformio.org/libraries/esp32async/ESPAsyncWebServer](https://registry.platformio.org/libraries/esp32async/ESPAsyncWebServer)
- Use: `lib_deps=ESP32Async/ESPAsyncWebServer` to point to latest version
- Use: `lib_deps=ESP32Async/ESPAsyncWebServer @ ^<x.y.z>` to point to latest version with the same major version
- Use: `lib_deps=ESP32Async/ESPAsyncWebServer @ <x.y.z>` to always point to the same version (reproductible build)
## Dependencies
### ESP32 / pioarduino
```ini
[env:stable]
platform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip
lib_compat_mode = strict
lib_ldf_mode = chain
lib_deps =
ESP32Async/AsyncTCP
ESP32Async/ESPAsyncWebServer
```
### ESP8266 / pioarduino
```ini
[env:stable]
platform = espressif8266
lib_compat_mode = strict
lib_ldf_mode = chain
lib_deps =
ESP32Async/ESPAsyncTCP
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**
AsyncTCPSock can be used instead of AsyncTCP by excluding AsyncTCP from the library dependencies and adding AsyncTCPSock instead:
```ini
lib_compat_mode = strict
lib_ldf_mode = chain
lib_deps =
https://github.com/ESP32Async/AsyncTCPSock/archive/refs/tags/v1.0.3-dev.zip
ESP32Async/ESPAsyncWebServer
lib_ignore =
AsyncTCP
ESP32Async/AsyncTCP
```
**RPAsyncTCP**
RPAsyncTCP replaces AsyncTCP to provide support for RP2040(+WiFi) and RP2350(+WiFi) boards. For example - Raspberry Pi Pico W and Raspberry Pi Pico 2W.
```ini
lib_compat_mode = strict
lib_ldf_mode = chain
platform = https://github.com/maxgerhardt/platform-raspberrypi.git
board = rpipicow
board_build.core = earlephilhower
lib_deps =
ayushsharma82/RPAsyncTCP@^1.3.2
ESP32Async/ESPAsyncWebServer
lib_ignore =
lwIP_ESPHost
build_flags = ${env.build_flags}
-Wno-missing-field-initializers
```
## Important recommendations for build options
Most of the crashes are caused by improper use or configuration of the AsyncTCP library used for the project.
Here are some recommendations to avoid them and build-time flags you can change.
`CONFIG_ASYNC_TCP_MAX_ACK_TIME` - defines a timeout for TCP connection to be considered alive when waiting for data.
In some bad network conditions you might consider increasing it.
`CONFIG_ASYNC_TCP_QUEUE_SIZE` - defines the length of the queue for events related to connections handling.
Both the server and AsyncTCP library were optimized to control the queue automatically. Do NOT try blindly increasing the queue size, it does not help you in a way you might think it is. If you receive debug messages about queue throttling, try to optimize your server callbacks code to execute as fast as possible.
Read #165 thread, it might give you some hints.
`CONFIG_ASYNC_TCP_RUNNING_CORE` - CPU core thread affinity that runs the queue events handling and executes server callbacks. Default is ANY core, so it means that for dualcore SoCs both cores could handle server activities. If your server's code is too heavy and unoptimized or you see that sometimes
server might affect other network activities, you might consider to bind it to the same core that runs Arduino code (1) to minimize affect on radio part. Otherwise you can leave the default to let RTOS decide where to run the thread based on priority
`CONFIG_ASYNC_TCP_STACK_SIZE` - stack size for the thread that runs sever events and callbacks. Default is 16k that is a way too much waste for well-defined short async code or simple static file handling. You might want to cosider reducing it to 4-8k to same RAM usage. If you do not know what this is or not sure about your callback code demands - leave it as default, should be enough even for very hungry callbacks in most cases.
> [!NOTE]
> This relates to ESP32 only, ESP8266 uses different ESPAsyncTCP lib that does not has this build options
I personally use the following configuration in my projects:
```c++
-D CONFIG_ASYNC_TCP_MAX_ACK_TIME=5000 // (keep default)
-D CONFIG_ASYNC_TCP_PRIORITY=10 // (keep default)
-D CONFIG_ASYNC_TCP_QUEUE_SIZE=64 // (keep default)
-D CONFIG_ASYNC_TCP_RUNNING_CORE=1 // force async_tcp task to be on same core as Arduino app (default is any core)
-D CONFIG_ASYNC_TCP_STACK_SIZE=4096 // reduce the stack size (default is 16K)
```
If you need to serve chunk requests with a really low buffer (which should be avoided), you can set `-D ASYNCWEBSERVER_USE_CHUNK_INFLIGHT=0` to disable the in-flight control.

File diff suppressed because it is too large Load Diff

View File

@@ -1,128 +0,0 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socioeconomic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
- Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
- The use of sexualized language or imagery, and sexual attention or
advances of any kind
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email
address, without their explicit permission
- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
https://sidweb.nl/cms3/en/contact.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

View File

@@ -1,74 +0,0 @@
# Concepts
## Principles of operation
### The Async Web server
- Listens for connections
- Wraps the new clients into `Request`
- Keeps track of clients and cleans memory
- Manages `Rewrites` and apply them on the request url
- Manages `Handlers` and attaches them to Requests
### Request Life Cycle
- TCP connection is received by the server
- The connection is wrapped inside `Request` object
- When the request head is received (type, url, get params, http version and host),
the server goes through all `Rewrites` (in the order they were added) to rewrite the url and inject query parameters,
next, it goes through all attached `Handlers`(in the order they were added) trying to find one
that `canHandle` the given request. If none are found, the default(catch-all) handler is attached.
- The rest of the request is received, calling the `handleUpload` or `handleBody` methods of the `Handler` if they are needed (POST+File/Body)
- When the whole request is parsed, the result is given to the `handleRequest` method of the `Handler` and is ready to be responded to
- In the `handleRequest` method, to the `Request` is attached a `Response` object (see below) that will serve the response data back to the client
- When the `Response` is sent, the client is closed and freed from the memory
### Rewrites and how do they work
- The `Rewrites` are used to rewrite the request url and/or inject get parameters for a specific request url path.
- All `Rewrites` are evaluated on the request in the order they have been added to the server.
- The `Rewrite` will change the request url only if the request url (excluding get parameters) is fully match
the rewrite url, and when the optional `Filter` callback return true.
- Setting a `Filter` to the `Rewrite` enables to control when to apply the rewrite, decision can be based on
request url, http version, request host/port/target host, get parameters or the request client's localIP or remoteIP.
- Two filter callbacks are provided: `ON_AP_FILTER` to execute the rewrite when request is made to the AP interface,
`ON_STA_FILTER` to execute the rewrite when request is made to the STA interface.
- The `Rewrite` can specify a target url with optional get parameters, e.g. `/to-url?with=params`
### Handlers and how do they work
- The `Handlers` are used for executing specific actions to particular requests
- One `Handler` instance can be attached to any request and lives together with the server
- Setting a `Filter` to the `Handler` enables to control when to apply the handler, decision can be based on
request url, http version, request host/port/target host, get parameters or the request client's localIP or remoteIP.
- Two filter callbacks are provided: `ON_AP_FILTER` to execute the rewrite when request is made to the AP interface,
`ON_STA_FILTER` to execute the rewrite when request is made to the STA interface.
- The `canHandle` method is used for handler specific control on whether the requests can be handled
and for declaring any interesting headers that the `Request` should parse. Decision can be based on request
method, request url, http version, request host/port/target host and get parameters
- Once a `Handler` is attached to given `Request` (`canHandle` returned true)
that `Handler` takes care to receive any file/data upload and attach a `Response`
once the `Request` has been fully parsed
- `Handlers` are evaluated in the order they are attached to the server. The `canHandle` is called only
if the `Filter` that was set to the `Handler` return true.
- The first `Handler` that can handle the request is selected, not further `Filter` and `canHandle` are called.
### Responses and how do they work
- The `Response` objects are used to send the response data back to the client
- The `Response` object lives with the `Request` and is freed on end or disconnect
- Different techniques are used depending on the response type to send the data in packets
returning back almost immediately and sending the next packet when this one is received.
Any time in between is spent to run the user loop and handle other network packets
- Responding asynchronously is probably the most difficult thing for most to understand
- Many different options exist for the user to make responding a background task
### Template processing
- ESPAsyncWebserver contains simple template processing engine.
- Template processing can be added to most response types.
- Currently it supports only replacing template placeholders with actual values. No conditional processing, cycles, etc.
- Placeholders are delimited with `%` symbols. Like this: `%TEMPLATE_PLACEHOLDER%`.
- It works by extracting placeholder name from response text and passing it to user provided function which should return actual value to be used instead of placeholder.
- Since it's user provided function, it is possible for library users to implement conditional processing and cycles themselves.
- Since it's impossible to know the actual response size after template processing step in advance (and, therefore, to include it in response headers), the response becomes [chunked](responses.md#chunked-response).

View File

@@ -1,40 +0,0 @@
# Configuration
## Important recommendations for build options
Most of the crashes are caused by improper use or configuration of the AsyncTCP library used for the project.
Here are some recommendations to avoid them and build-time flags you can change.
- `CONFIG_ASYNC_TCP_MAX_ACK_TIME`: defines a timeout for TCP connection to be considered alive when waiting for data.
In some bad network conditions you might consider increasing it.
- `CONFIG_ASYNC_TCP_QUEUE_SIZE`: defines the length of the queue for events related to connections handling.
Both the server and AsyncTCP library were optimized to control the queue automatically. Do NOT try blindly increasing the queue size, it does not help you in a way you might think it is. If you receive debug messages about queue throttling, try to optimize your server callbacks code to execute as fast as possible.
- `CONFIG_ASYNC_TCP_RUNNING_CORE`: CPU core thread affinity that runs the queue events handling and executes server callbacks. Default is ANY core, so it means that for dualcore SoCs both cores could handle server activities. If your server's code is too heavy and unoptimized or you see that sometimes
server might affect other network activities, you might consider to bind it to the same core that runs Arduino code (1) to minimize affect on radio part. Otherwise you can leave the default to let RTOS decide where to run the thread based on priority
- `CONFIG_ASYNC_TCP_STACK_SIZE`: stack size for the thread that runs sever events and callbacks. Default is 16k that is a way too much waste for well-defined short async code or simple static file handling. You might want to cosider reducing it to 4-8k to same RAM usage. If you do not know what this is or not sure about your callback code demands - leave it as default, should be enough even for very hungry callbacks in most cases.
- `ASYNCWEBSERVER_USE_CHUNK_INFLIGHT`: inflight control for chunked responses.
If you need to serve chunk requests with a really low buffer (which should be avoided), you can set `-D ASYNCWEBSERVER_USE_CHUNK_INFLIGHT=0` to disable the in-flight control.
> [!NOTE]
> This relates to ESP32 only, ESP8266 uses different ESPAsyncTCP lib that does not has this build options
Recommended configurations:
```c++
-D CONFIG_ASYNC_TCP_MAX_ACK_TIME=5000 // (keep default)
-D CONFIG_ASYNC_TCP_PRIORITY=10 // (keep default)
-D CONFIG_ASYNC_TCP_QUEUE_SIZE=64 // (keep default)
-D CONFIG_ASYNC_TCP_RUNNING_CORE=1 // force async_tcp task to be on same core as Arduino app (default is any core)
-D CONFIG_ASYNC_TCP_STACK_SIZE=4096 // reduce the stack size (default is 16K)
```
## Important things to remember
- This is fully asynchronous server and as such does not run on the loop thread.
- You can not use yield or delay or any function that uses them inside the callbacks
- The server is smart enough to know when to close the connection and free resources
- You can not send more than one response to a single request

View File

@@ -1,92 +0,0 @@
# EventSource
## Async Event Source Plugin
The server includes EventSource (Server-Sent Events) plugin which can be used to send short text events to the browser.
Difference between EventSource and WebSockets is that EventSource is single direction, text-only protocol.
See the [ServerSentEvents example here](https://github.com/ESP32Async/ESPAsyncWebServer/blob/master/examples/ServerSentEvents/ServerSentEvents.ino).
### Setup Event Source on the server
```cpp
AsyncWebServer server(80);
AsyncEventSource events("/events");
void setup(){
// setup ......
events.onConnect([](AsyncEventSourceClient *client){
if(client->lastId()){
Serial.printf("Client reconnected! Last message ID that it got is: %u\n", client->lastId());
}
//send event with message "hello!", id current millis
// and set reconnect delay to 1 second
client->send("hello!",NULL,millis(),1000);
});
server.addHandler(&events);
// setup ......
}
void loop(){
if(eventTriggered){ // your logic here
//send event "myevent"
events.send("my event content","myevent",millis());
}
}
```
**IMPORTANT**: Use `AsyncAuthenticationMiddleware` instead of the deprecated `setAuthentication()` method for authentication.
```cpp
AsyncAuthenticationMiddleware authMiddleware;
authMiddleware.setAuthType(AsyncAuthType::AUTH_DIGEST);
authMiddleware.setRealm("My app name");
authMiddleware.setUsername("admin");
authMiddleware.setPassword("admin");
authMiddleware.generateHash();
events.addMiddleware(&authMiddleware);
```
### Setup Event Source in the browser
```javascript
if (!!window.EventSource) {
var source = new EventSource("/events");
source.addEventListener(
"open",
function (e) {
console.log("Events Connected");
},
false,
);
source.addEventListener(
"error",
function (e) {
if (e.target.readyState != EventSource.OPEN) {
console.log("Events Disconnected");
}
},
false,
);
source.addEventListener(
"message",
function (e) {
console.log("message", e.data);
},
false,
);
source.addEventListener(
"myevent",
function (e) {
console.log("myevent", e.data);
},
false,
);
}
```

View File

@@ -1,67 +0,0 @@
# Filters
## Using filters
Filters can be set to `Rewrite` or `Handler` in order to control when to apply the rewrite and consider the handler.
A filter is a callback function that evaluates the request and return a boolean `true` to include the item
or `false` to exclude it.
Two filter callback are provided for convince:
- `ON_STA_FILTER` - return true when requests are made to the STA (station mode) interface.
- `ON_AP_FILTER` - return true when requests are made to the AP (access point) interface.
See the [Filters example here](https://github.com/ESP32Async/ESPAsyncWebServer/blob/master/examples/Filters/Filters.ino).
### Serve different site files in AP mode
```cpp
// For SPIFFS
server.serveStatic("/", SPIFFS, "/www/").setFilter(ON_STA_FILTER);
server.serveStatic("/", SPIFFS, "/ap/").setFilter(ON_AP_FILTER);
// For LittleFS
server.serveStatic("/", LittleFS, "/www/").setFilter(ON_STA_FILTER);
server.serveStatic("/", LittleFS, "/ap/").setFilter(ON_AP_FILTER);
```
### Rewrite to different index on AP
```cpp
// Serve the file "/www/index-ap.htm" in AP, and the file "/www/index.htm" on STA (SPIFFS)
server.rewrite("/", "index.htm");
server.rewrite("/index.htm", "index-ap.htm").setFilter(ON_AP_FILTER);
server.serveStatic("/", SPIFFS, "/www/");
// Serve the file "/www/index-ap.htm" in AP, and the file "/www/index.htm" on STA (LittleFS)
server.rewrite("/", "index.htm");
server.rewrite("/index.htm", "index-ap.htm").setFilter(ON_AP_FILTER);
server.serveStatic("/", LittleFS, "/www/");
```
### Serving different hosts
```cpp
// Filter callback using request host
bool filterOnHost1(AsyncWebServerRequest *request) { return request->host() == "host1"; }
// Server setup: server files in "/host1/" to requests for "host1", and files in "/www/" otherwise (SPIFFS).
server.serveStatic("/", SPIFFS, "/host1/").setFilter(filterOnHost1);
server.serveStatic("/", SPIFFS, "/www/");
// Server setup: server files in "/host1/" to requests for "host1", and files in "/www/" otherwise (LittleFS).
server.serveStatic("/", LittleFS, "/host1/").setFilter(filterOnHost1);
server.serveStatic("/", LittleFS, "/www/");
```
### Determine interface inside callbacks
```cpp
String RedirectUrl = "http://";
if (ON_STA_FILTER(request)) {
RedirectUrl += WiFi.localIP().toString();
} else {
RedirectUrl += WiFi.softAPIP().toString();
}
RedirectUrl += "/index.htm";
request->redirect(RedirectUrl);
```

View File

@@ -1,21 +0,0 @@
# Help & Issues
We welcome contributions and questions from the community!
## Getting Help
If you have questions about how to use the library, need help with your code, or want to discuss features:
- **Please do not open a GitHub Issue.** Issues are reserved for bug reports and verifiable feature requests.
- Instead, **open a discussion** in the [GitHub Discussions tab](https://github.com/ESP32Async/ESPAsyncWebServer/discussions). This is the best place to get help from maintainers and other users.
## Reporting Issues
If you believe you have found a bug in the library, or if you have a specific feature request:
- Please **open an Issue** on the [GitHub Issues tab](https://github.com/ESP32Async/ESPAsyncWebServer/issues).
- Be sure to include as much detail as possible, including:
- The version of the library you are using.
- The hardware you are running on (e.g., ESP32 S3, ESP8266, etc).
- A minimal code example that reproduces the issue.
- Any relevant logs or error messages (Enable Core Debug Level to Verbose).

View File

@@ -1,53 +0,0 @@
# ESPAsyncWebServer
![https://avatars.githubusercontent.com/u/195753706?s=96&v=4](https://avatars.githubusercontent.com/u/195753706?s=96&v=4)
[![Latest Release](https://img.shields.io/github/release/ESP32Async/ESPAsyncWebServer.svg)](https://GitHub.com/ESP32Async/ESPAsyncWebServer/releases/)
[![PlatformIO Registry](https://badges.registry.platformio.org/packages/ESP32Async/library/ESPAsyncWebServer.svg)](https://registry.platformio.org/libraries/ESP32Async/ESPAsyncWebServer)
[![License: LGPL 3.0](https://img.shields.io/badge/License-LGPL%203.0-yellow.svg)](https://opensource.org/license/lgpl-3-0/)
[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](code_of_conduct.md)
[![GitHub latest commit](https://badgen.net/github/last-commit/ESP32Async/ESPAsyncWebServer)](https://GitHub.com/ESP32Async/ESPAsyncWebServer/commit/)
[![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/ESP32Async/ESPAsyncWebServer)
[![Documentation](https://img.shields.io/badge/Wiki-ESPAsyncWebServer-blue?logo=github)](https://github.com/ESP32Async/ESPAsyncWebServer/wiki)
Asynchronous HTTP and WebSocket Server Library for ESP32, ESP8266, RP2040 and RP2350
Supports: WebSocket, SSE, Authentication, Arduino Json 7, MessagePack, File Upload, Static File serving, URL Rewrite, URL Redirect, etc.
## Overview
- Using asynchronous network means that you can handle more than one connection at the same time
- You are called once the request is ready and parsed
- When you send the response, you are immediately ready to handle other connections
while the server is taking care of sending the response in the background
- Speed is OMG
- Easy to use API, HTTP Basic and Digest MD5 Authentication (default), ChunkedResponse
- Easily extendible to handle any type of content
- Supports Continue 100
- Async WebSocket plugin offering different locations without extra servers or ports
- Async EventSource (Server-Sent Events) plugin to send events to the browser
- URL Rewrite plugin for conditional and permanent url rewrites
- ServeStatic plugin that supports cache, Last-Modified, default index and more
- Simple template processing engine to handle templates
## Libraries and projects that use AsyncWebServer
- [Beelance](https://beelance.carbou.me/) - Autonomous and remotely connected weight scale for beehives 🐝
- [ElegantOTA](https://github.com/ayushsharma82/ElegantOTA) - OTA updates made slick and simple for everyone!
- [ESP Radio](https://github.com/Edzelf/Esp-radio) - Icecast radio based on ESP8266 and VS1053
- [ESP-DASH](https://github.com/ayushsharma82/ESP-DASH) - ESP-DASH is a library for ESP32 Arduino that facilitates the use of a dashboard in an asynchronous way. I have contributed most of the recently newly added fixes and features of the OSS and Pro version
- [ESP-RFID](https://github.com/omersiar/esp-rfid) - MFRC522 RFID Access Control Management project for ESP8266.
- [ESPurna](https://github.com/xoseperez/espurna) - ESPurna ("spark" in Catalan) is a custom C firmware for ESP8266 based smart switches. It was originally developed with the ITead Sonoff in mind.
- [FauxmoESP](https://github.com/vintlabs/fauxmoESP) - Belkin WeMo emulator library for ESP8266.
- [MycilaESPConnect](https://mathieu.carbou.me/MycilaESPConnect) - Simple & Easy Network Manager with Captive Portal for ESP32 supporting Ethernet
- [MycilaJSY](https://mathieu.carbou.me/MycilaJSY) - Arduino / ESP32 library for the JSY1031, JSY-MK-163, JSY-MK-193, JSY-MK-194, JSY-MK-227, JSY-MK-229, JSY-MK-333 families single-phase and three-phase AC bidirectional meters from Shenzhen Jiansiyan Technologies Co, Ltd.
- [MycilaWebSerial](https://mathieu.carbou.me/MycilaWebSerial) - WebSerial is a Serial Monitor for ESP32 that can be accessed remotely via a web browser
- [NetWizard](https://github.com/ayushsharma82/NetWizard) - No need to hard-code WiFi credentials ever again. (ESP32, RP2040+W)
- [Sattrack](https://github.com/Hopperpop/Sattrack) - Track the ISS with ESP8266
- [VZero](https://github.com/andig/vzero) - the Wireless zero-config controller for volkszaehler.org
- [WebSerial](https://github.com/ayushsharma82/WebSerial) - A remote terminal library for wireless microcontrollers to log, monitor or debug your firmware/product
- [WebSocketToSerial](https://github.com/hallard/WebSocketToSerial) - Debug serial devices through the web browser
- [YaSolR (Yet another Solar Router)](https://yasolr.carbou.me) - Heat water with your Solar Production Excess with the more powerful and precise solar diverter out there!

View File

@@ -1,96 +0,0 @@
# Installation
## Compatibility
- ESP32, ESP8266, RP2040, RP2350
- Arduino Core 2.x and 3.x
## How to install
The library can be downloaded from the releases page at [https://github.com/ESP32Async/ESPAsyncWebServer/releases](https://github.com/ESP32Async/ESPAsyncWebServer/releases).
It is also deployed in these registries:
- Arduino Library Registry: [https://github.com/arduino/library-registry](https://github.com/arduino/library-registry)
- Espressif Component Registry [https://components.espressif.com/components/esp32async/espasyncwebserver](https://components.espressif.com/components/esp32async/espasyncwebserver)
- PlatformIO Registry: [https://registry.platformio.org/libraries/ESP32Async/ESPAsyncWebServer](https://registry.platformio.org/libraries/ESP32Async/ESPAsyncWebServer)
- Use: `lib_deps=ESP32Async/ESPAsyncWebServer` to point to latest version
- Use: `lib_deps=ESP32Async/ESPAsyncWebServer @ ^<x.y.z>` to point to latest version with the same major version
- Use: `lib_deps=ESP32Async/ESPAsyncWebServer @ <x.y.z>` to always point to the same version (reproducible build)
## Dependencies
### ESP32 / pioarduino
```ini
[env:stable]
platform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip
lib_compat_mode = strict
lib_ldf_mode = chain
lib_deps =
ESP32Async/AsyncTCP
ESP32Async/ESPAsyncWebServer
```
### ESP8266 / pioarduino
```ini
[env:stable]
platform = espressif8266
lib_compat_mode = strict
lib_ldf_mode = chain
lib_deps =
ESP32Async/ESPAsyncTCP
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**
AsyncTCPSock can be used instead of AsyncTCP by excluding AsyncTCP from the library dependencies and adding AsyncTCPSock instead:
```ini
lib_compat_mode = strict
lib_ldf_mode = chain
lib_deps =
https://github.com/ESP32Async/AsyncTCPSock/archive/refs/tags/v1.0.3-dev.zip
ESP32Async/ESPAsyncWebServer
lib_ignore =
AsyncTCP
ESP32Async/AsyncTCP
```
**RPAsyncTCP**
RPAsyncTCP replaces AsyncTCP to provide support for RP2040(+WiFi) and RP2350(+WiFi) boards. For example - Raspberry Pi Pico W and Raspberry Pi Pico 2W.
```ini
lib_compat_mode = strict
lib_ldf_mode = chain
platform = https://github.com/maxgerhardt/platform-raspberrypi.git
board = rpipicow
board_build.core = earlephilhower
lib_deps =
ayushsharma82/RPAsyncTCP@^1.3.2
ESP32Async/ESPAsyncWebServer
lib_ignore =
lwIP_ESPHost
build_flags = ${env.build_flags}
-Wno-missing-field-initializers
```

Binary file not shown.

Before

Width:  |  Height:  |  Size: 479 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 KiB

View File

@@ -1,125 +0,0 @@
# Middleware
## Middleware
### How to use Middleware
Middleware is a way to intercept requests to perform some operations on them, like authentication, authorization, logging, etc and also act on the response headers.
Middleware can either be attached to individual handlers, attached at the server level (thus applied to all handlers), or both.
They will be executed in the order they are attached, and they can stop the request processing by sending a response themselves.
You can have a look at the [examples](https://github.com/ESP32Async/ESPAsyncWebServer/tree/master/examples) for some use cases.
For example, such middleware would handle authentication and set some attributes on the request to make them available for the next middleware and for the handler which will process the request.
```c++
AsyncMiddlewareFunction complexAuth([](AsyncWebServerRequest* request, ArMiddlewareNext next) {
if (!request->authenticate("user", "password")) {
return request->requestAuthentication();
}
request->setAttribute("user", "Mathieu");
request->setAttribute("role", "staff");
next(); // continue processing
// you can act one the response object
request->getResponse()->addHeader("X-Rate-Limit", "200");
});
```
**Here are the list of available middlewares:**
- `AsyncMiddlewareFunction`: can convert a lambda function (`ArMiddlewareCallback`) to a middleware
- `AsyncAuthenticationMiddleware`: to handle basic/digest authentication globally or per handler
- `AsyncAuthorizationMiddleware`: to handle authorization globally or per handler
- `AsyncCorsMiddleware`: to handle CORS preflight request globally or per handler
- `AsyncHeaderFilterMiddleware`: to filter out headers from the request
- `AsyncHeaderFreeMiddleware`: to only keep some headers from the request, and remove the others
- `LoggerMiddleware`: to log requests globally or per handler with the same pattern as curl. Will also record request processing time
- `AsyncRateLimitMiddleware`: to limit the number of requests on a windows of time globally or per handler
See the [Middleware example here](https://github.com/ESP32Async/ESPAsyncWebServer/blob/master/examples/Middleware/Middleware.ino).
### CORS with AsyncCorsMiddleware
You can handle CORS preflight requests and headers using `AsyncCorsMiddleware`.
```cpp
static AsyncCorsMiddleware cors;
void setup() {
// Configure CORS
cors.setOrigin("http://my-client-app.com");
cors.setMethods("POST, GET, OPTIONS, DELETE");
cors.setHeaders("X-Custom-Header");
cors.setAllowCredentials(true);
cors.setMaxAge(600);
// Add middleware to server (valid for all handlers)
server.addMiddleware(&cors);
// Or add to specific handler only
// server.on("/api/data", HTTP_GET, handleData).addMiddleware(&cors);
}
```
### Rate Limiting with AsyncRateLimitMiddleware
You can limit the number of requests within a time window using `AsyncRateLimitMiddleware`.
```cpp
static AsyncRateLimitMiddleware rateLimit;
void setup() {
// Allow maximum 5 requests per 10 seconds
rateLimit.setMaxRequests(5);
rateLimit.setWindowSize(10);
// Apply globally
// server.addMiddleware(&rateLimit);
// OR apply to specific sensitive endpoint
server.on("/login", HTTP_POST, handleLogin).addMiddleware(&rateLimit);
}
```
### Authentication with AsyncAuthenticationMiddleware
**IMPORTANT**: Do not use the `setUsername()` and `setPassword()` methods on the handlers anymore.
They are deprecated.
These methods were causing a copy of the username and password for each handler, which is not efficient.
Now, you can use the `AsyncAuthenticationMiddleware` to handle authentication globally or per handler.
```c++
AsyncAuthenticationMiddleware authMiddleware;
// [...]
authMiddleware.setAuthType(AsyncAuthType::AUTH_DIGEST);
authMiddleware.setRealm("My app name");
authMiddleware.setUsername("admin");
authMiddleware.setPassword("admin");
authMiddleware.setAuthFailureMessage("Authentication failed");
authMiddleware.generateHash(); // optimization to avoid generating the hash at each request
// [...]
server.addMiddleware(&authMiddleware); // globally add authentication to the server
// [...]
myHandler.addMiddleware(&authMiddleware); // add authentication to a specific handler
```
See the [Auth example here](https://github.com/ESP32Async/ESPAsyncWebServer/blob/master/examples/Auth/Auth.ino).
### Migration to Middleware to improve performance and memory usage
- `AsyncEventSource.authorizeConnect(...)` => do not use this method anymore: add a common `AsyncAuthorizationMiddleware` to the handler or server, and make sure to add it AFTER the `AsyncAuthenticationMiddleware` if you use authentication.
- `AsyncWebHandler.setAuthentication(...)` => do not use this method anymore: add a common `AsyncAuthenticationMiddleware` to the handler or server
- `ArUploadHandlerFunction` and `ArBodyHandlerFunction` => these callbacks receiving body data and upload and not calling anymore the authentication code for performance reasons.
These callbacks can be called multiple times during request parsing, so this is up to the user to now call the `AsyncAuthenticationMiddleware.allowed(request)` if needed and ideally when the method is called for the first time.
These callbacks are also not triggering the whole middleware chain since they are not part of the request processing workflow (they are not the final handler).

View File

@@ -1,197 +0,0 @@
# Requests
## Request Variables
### Common Variables
```cpp
request->version(); // uint8_t: 0 = HTTP/1.0, 1 = HTTP/1.1
request->method(); // enum: HTTP_GET, HTTP_POST, HTTP_DELETE, HTTP_PUT, HTTP_PATCH, HTTP_HEAD, HTTP_OPTIONS
request->url(); // String: URL of the request (not including host, port or GET parameters)
request->host(); // String: The requested host (can be used for virtual hosting)
request->contentType(); // String: ContentType of the request (not available in Handler::canHandle)
request->contentLength(); // size_t: ContentLength of the request (not available in Handler::canHandle)
request->multipart(); // bool: True if the request has content type "multipart"
```
### Headers
```cpp
//List all collected headers
int headers = request->headers();
int i;
for(i=0;i<headers;i++){
const AsyncWebHeader* h = request->getHeader(i);
Serial.printf("HEADER[%s]: %s\n", h->name().c_str(), h->value().c_str());
}
//get specific header by name
if(request->hasHeader("MyHeader")){
const AsyncWebHeader* h = request->getHeader("MyHeader");
Serial.printf("MyHeader: %s\n", h->value().c_str());
}
//List all collected headers (Compatibility)
int headers = request->headers();
int i;
for(i=0;i<headers;i++){
Serial.printf("HEADER[%s]: %s\n", request->headerName(i).c_str(), request->header(i).c_str());
}
//get specific header by name (Compatibility)
if(request->hasHeader("MyHeader")){
Serial.printf("MyHeader: %s\n", request->header("MyHeader").c_str());
}
```
See the [Headers example here](https://github.com/ESP32Async/ESPAsyncWebServer/blob/master/examples/Headers/Headers.ino).
## Path Variable
With path variable you can create a custom regex rule for a specific parameter in a route.
For example we want a `sensorId` parameter in a route rule to match only a integer.
```cpp
server.on("^\\/sensor\\/([0-9]+)$", HTTP_GET, [] (AsyncWebServerRequest *request) {
String sensorId = request->pathArg(0);
});
```
_NOTE_: All regex patterns starts with `^` and ends with `$`
To enable the `Path variable` support, you have to define the buildflag `-DASYNCWEBSERVER_REGEX`.
For Arduino IDE create/update `platform.local.txt`:
`Windows`: C:\Users\(username)\AppData\Local\Arduino15\packages\\`{espxxxx}`\hardware\\`espxxxx`\\`{version}`\platform.local.txt
`Linux`: ~/.arduino15/packages/`{espxxxx}`/hardware/`{espxxxx}`/`{version}`/platform.local.txt
Add/Update the following line:
```
compiler.cpp.extra_flags=-DASYNCWEBSERVER_REGEX
```
For platformio modify `platformio.ini`:
```ini
[env:myboard]
build_flags =
-DASYNCWEBSERVER_REGEX
```
_NOTE_: By enabling `ASYNCWEBSERVER_REGEX`, `<regex>` will be included. This will add an 100k to your binary.
See the [URIMatcher example here](https://github.com/ESP32Async/ESPAsyncWebServer/blob/master/examples/URIMatcher/URIMatcher.ino) and [URIMatcherTest example here](https://github.com/ESP32Async/ESPAsyncWebServer/blob/master/examples/URIMatcherTest/URIMatcherTest.ino).
### GET, POST and FILE parameters
```cpp
//List all parameters
int params = request->params();
for(int i=0;i<params;i++){
AsyncWebParameter* p = request->getParam(i);
if(p->isFile()){ //p->isPost() is also true
Serial.printf("FILE[%s]: %s, size: %u\n", p->name().c_str(), p->value().c_str(), p->size());
} else if(p->isPost()){
Serial.printf("POST[%s]: %s\n", p->name().c_str(), p->value().c_str());
} else {
Serial.printf("GET[%s]: %s\n", p->name().c_str(), p->value().c_str());
}
}
//Check if GET parameter exists
if(request->hasParam("download"))
AsyncWebParameter* p = request->getParam("download");
//Check if POST (but not File) parameter exists
if(request->hasParam("download", true))
AsyncWebParameter* p = request->getParam("download", true);
//Check if FILE was uploaded
if(request->hasParam("download", true, true))
AsyncWebParameter* p = request->getParam("download", true, true);
//List all parameters (Compatibility)
int args = request->args();
for(int i=0;i<args;i++){
Serial.printf("ARG[%s]: %s\n", request->argName(i).c_str(), request->arg(i).c_str());
}
//Check if parameter exists (Compatibility)
if(request->hasArg("download"))
String arg = request->arg("download");
```
See the [Params example here](https://github.com/ESP32Async/ESPAsyncWebServer/blob/master/examples/Params/Params.ino).
### FILE Upload handling
```cpp
void handleUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final){
if(!index){
Serial.printf("UploadStart: %s\n", filename.c_str());
}
for(size_t i=0; i<len; i++){
Serial.write(data[i]);
}
if(final){
Serial.printf("UploadEnd: %s, %u B\n", filename.c_str(), index+len);
}
}
```
See the [Upload example here](https://github.com/ESP32Async/ESPAsyncWebServer/blob/master/examples/Upload/Upload.ino).
### Body data handling
```cpp
void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total){
if(!index){
Serial.printf("BodyStart: %u B\n", total);
}
for(size_t i=0; i<len; i++){
Serial.write(data[i]);
}
if(index + len == total){
Serial.printf("BodyEnd: %u B\n", total);
}
}
```
If needed, the `_tempObject` field on the request can be used to store a pointer to temporary data (e.g. from the body) associated with the request. If assigned, the pointer will automatically be freed along with the request.
### JSON body handling with ArduinoJson
Endpoints which consume JSON can use a special handler to get ready to use JSON data in the request callback:
```cpp
#include "AsyncJson.h"
#include "ArduinoJson.h"
AsyncCallbackJsonWebHandler* handler = new AsyncCallbackJsonWebHandler("/rest/endpoint", [](AsyncWebServerRequest *request, JsonVariant &json) {
JsonObject jsonObj = json.as<JsonObject>();
// ...
});
server.addHandler(handler);
```
See the [Json example here](https://github.com/ESP32Async/ESPAsyncWebServer/blob/master/examples/Json/Json.ino).
### MessagePack body handling
Endpoints which consume MessagePack can use a special handler to get ready to use MessagePack data in the request callback:
```cpp
#include "AsyncMessagePack.h"
#include "ArduinoJson.h"
AsyncCallbackMessagePackWebHandler* handler = new AsyncCallbackMessagePackWebHandler("/rest/endpoint", [](AsyncWebServerRequest *request, JsonVariant &json) {
JsonObject jsonObj = json.as<JsonObject>();
// ...
});
server.addHandler(handler);
```
See the [MessagePack example here](https://github.com/ESP32Async/ESPAsyncWebServer/blob/master/examples/MessagePack/MessagePack.ino).

View File

@@ -1,693 +0,0 @@
# Responses
## Replace a Response
```c++
// It is possible to replace a response.
// The previous one will be deleted.
// Response sending happens when the handler returns.
server.on("/replace", HTTP_GET, [](AsyncWebServerRequest* request) {
request->send(200, "text/plain", "Hello, world");
// oups! finally we want to send a different response
request->send(400, "text/plain", "validation error");
});
```
This will send error 400 instead of 200.
See the [example here](https://github.com/ESP32Async/ESPAsyncWebServer/blob/master/examples/Replace/Replace.ino).
## Request Continuation
Request Continuation is the ability to pause the processing of a request (the actual sending over the network) to be able to let another task commit the response on the network later.
This is a common supported use case amongst web servers.
A usage example can be found in the example called [RequestContinuation.ino](https://github.com/ESP32Async/ESPAsyncWebServer/blob/main/examples/RequestContinuation/RequestContinuation.ino)
In the handler receiving the request, just execute:
```c++
AsyncWebServerRequestPtr ptr = request->pause();
```
This will pause the request and return a `AsyncWebServerRequestPtr` (this is a weak pointer).
**The AsyncWebServerRequestPtr is the ONLY object authorized to leave the scope of the request handler.**
Save somewhere this smart pointer and use it later to commit the response like this:
```c++
// you can check for expiration
if (requestPtr.expired()) {
// the request connection was closed some time ago so the request is not accessible anymore
} else if (longRunningTaskFinished) {
// this is what you always need to do when you want to access the request.
if (auto request = requestPtr.lock()) {
// send back the response
request->send(200, contentType, ...);
} else {
// the connection has been closed so the request is not accessible anymore
}
}
```
Most of the time you can simply do like below if checking expiration is not needed:
```c++
if (auto request = requestPtr.lock()) {
// send back the response
request->send(200, contentType, ...);
}
```
See the [RequestContinuation example here](https://github.com/ESP32Async/ESPAsyncWebServer/blob/master/examples/RequestContinuation/RequestContinuation.ino) and [RequestContinuationComplete example here](https://github.com/ESP32Async/ESPAsyncWebServer/blob/master/examples/RequestContinuationComplete/RequestContinuationComplete.ino).
## Responses
### Redirect to another URL
```cpp
//to local url
request->redirect("/login");
//to external url
request->redirect("http://esp8266.com");
```
See the [Redirect example here](https://github.com/ESP32Async/ESPAsyncWebServer/blob/master/examples/Redirect/Redirect.ino).
### Basic response with HTTP Code
```cpp
request->send(404); //Sends 404 File Not Found
```
### Basic response with HTTP Code and extra headers
```cpp
AsyncWebServerResponse *response = request->beginResponse(404); //Sends 404 File Not Found
response->addHeader("Server","ESP Async Web Server");
request->send(response);
```
### Basic response with string content
```cpp
request->send(200, "text/plain", "Hello World!");
```
### Basic response with string content and extra headers
```cpp
AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", "Hello World!");
response->addHeader("Server","ESP Async Web Server");
request->send(response);
```
### Send large webpage from PROGMEM
```cpp
const char index_html[] PROGMEM = "..."; // large char array, tested with 14k
request->send_P(200, "text/html", index_html);
```
### Send large webpage from PROGMEM and extra headers
```cpp
const char index_html[] PROGMEM = "..."; // large char array, tested with 14k
AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", index_html);
response->addHeader("Server","ESP Async Web Server");
request->send(response);
```
### Send large webpage from PROGMEM containing templates
```cpp
String processor(const String& var)
{
if(var == "HELLO_FROM_TEMPLATE")
return F("Hello world!");
return String();
}
// ...
const char index_html[] PROGMEM = "..."; // large char array, tested with 14k
request->send_P(200, "text/html", index_html, processor);
```
See the [Templates example here](https://github.com/ESP32Async/ESPAsyncWebServer/blob/master/examples/Templates/Templates.ino).
### Send large webpage from PROGMEM containing templates and extra headers
```cpp
String processor(const String& var)
{
if(var == "HELLO_FROM_TEMPLATE")
return F("Hello world!");
return String();
}
// ...
const char index_html[] PROGMEM = "..."; // large char array, tested with 14k
AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", index_html, processor);
response->addHeader("Server","ESP Async Web Server");
request->send(response);
```
### Send binary content from PROGMEM
```cpp
//File: favicon.ico.gz, Size: 726
#define favicon_ico_gz_len 726
const uint8_t favicon_ico_gz[] PROGMEM = {
0x1F, 0x8B, 0x08, 0x08, 0x0B, 0x87, 0x90, 0x57, 0x00, 0x03, 0x66, 0x61, 0x76, 0x69, 0x63, 0x6F,
0x6E, 0x2E, 0x69, 0x63, 0x6F, 0x00, 0xCD, 0x53, 0x5F, 0x48, 0x9A, 0x51, 0x14, 0xBF, 0x62, 0x6D,
0x86, 0x96, 0xA9, 0x64, 0xD3, 0xFE, 0xA8, 0x99, 0x65, 0x1A, 0xB4, 0x8A, 0xA8, 0x51, 0x54, 0x23,
0xA8, 0x11, 0x49, 0x51, 0x8A, 0x34, 0x62, 0x93, 0x85, 0x31, 0x58, 0x44, 0x12, 0x45, 0x2D, 0x58,
0xF5, 0x52, 0x41, 0x10, 0x23, 0x82, 0xA0, 0x20, 0x98, 0x2F, 0xC1, 0x26, 0xED, 0xA1, 0x20, 0x89,
0x04, 0xD7, 0x83, 0x58, 0x20, 0x28, 0x04, 0xAB, 0xD1, 0x9B, 0x8C, 0xE5, 0xC3, 0x60, 0x32, 0x64,
0x0E, 0x56, 0xBF, 0x9D, 0xEF, 0xF6, 0x30, 0x82, 0xED, 0xAD, 0x87, 0xDD, 0x8F, 0xF3, 0xDD, 0x8F,
0x73, 0xCF, 0xEF, 0x9C, 0xDF, 0x39, 0xBF, 0xFB, 0x31, 0x26, 0xA2, 0x27, 0x37, 0x97, 0xD1, 0x5B,
0xCF, 0x9E, 0x67, 0x30, 0xA6, 0x66, 0x8C, 0x99, 0xC9, 0xC8, 0x45, 0x9E, 0x6B, 0x3F, 0x5F, 0x74,
0xA6, 0x94, 0x5E, 0xDB, 0xFF, 0xB2, 0xE6, 0xE7, 0xE7, 0xF9, 0xDE, 0xD6, 0xD6, 0x96, 0xDB, 0xD8,
0xD8, 0x78, 0xBF, 0xA1, 0xA1, 0xC1, 0xDA, 0xDC, 0xDC, 0x2C, 0xEB, 0xED, 0xED, 0x15, 0x9B, 0xCD,
0xE6, 0x4A, 0x83, 0xC1, 0xE0, 0x2E, 0x29, 0x29, 0x99, 0xD6, 0x6A, 0xB5, 0x4F, 0x75, 0x3A, 0x9D,
0x61, 0x75, 0x75, 0x95, 0xB5, 0xB7, 0xB7, 0xDF, 0xC8, 0xD1, 0xD4, 0xD4, 0xF4, 0xB0, 0xBA, 0xBA,
0xFA, 0x83, 0xD5, 0x6A, 0xFD, 0x5A, 0x5E, 0x5E, 0x9E, 0x28, 0x2D, 0x2D, 0x0D, 0x10, 0xC6, 0x4B,
0x98, 0x78, 0x5E, 0x5E, 0xDE, 0x95, 0x42, 0xA1, 0x40, 0x4E, 0x4E, 0xCE, 0x65, 0x76, 0x76, 0xF6,
0x47, 0xB5, 0x5A, 0x6D, 0x4F, 0x26, 0x93, 0xA2, 0xD6, 0xD6, 0x56, 0x8E, 0x6D, 0x69, 0x69, 0xD1,
0x11, 0x36, 0x62, 0xB1, 0x58, 0x60, 0x32, 0x99, 0xA0, 0xD7, 0xEB, 0x51, 0x58, 0x58, 0x88, 0xFC,
0xFC, 0x7C, 0x10, 0x16, 0x02, 0x56, 0x2E, 0x97, 0x43, 0x2A, 0x95, 0x42, 0x2C, 0x16, 0x23, 0x33,
0x33, 0x33, 0xAE, 0x52, 0xA9, 0x1E, 0x64, 0x65, 0x65, 0x71, 0x7C, 0x7D, 0x7D, 0xBD, 0x93, 0xEA,
0xFE, 0x30, 0x1A, 0x8D, 0xE8, 0xEC, 0xEC, 0xC4, 0xE2, 0xE2, 0x22, 0x6A, 0x6A, 0x6A, 0x40, 0x39,
0x41, 0xB5, 0x38, 0x4E, 0xC8, 0x33, 0x3C, 0x3C, 0x0C, 0x87, 0xC3, 0xC1, 0x6B, 0x54, 0x54, 0x54,
0xBC, 0xE9, 0xEB, 0xEB, 0x93, 0x5F, 0x5C, 0x5C, 0x30, 0x8A, 0x9D, 0x2E, 0x2B, 0x2B, 0xBB, 0xA2,
0x3E, 0x41, 0xBD, 0x21, 0x1E, 0x8F, 0x63, 0x6A, 0x6A, 0x0A, 0x81, 0x40, 0x00, 0x94, 0x1B, 0x3D,
0x3D, 0x3D, 0x42, 0x3C, 0x96, 0x96, 0x96, 0x70, 0x7E, 0x7E, 0x8E, 0xE3, 0xE3, 0x63, 0xF8, 0xFD,
0xFE, 0xB4, 0xD7, 0xEB, 0xF5, 0x8F, 0x8F, 0x8F, 0x5B, 0x68, 0x5E, 0x6F, 0x05, 0xCE, 0xB4, 0xE3,
0xE8, 0xE8, 0x08, 0x27, 0x27, 0x27, 0xD8, 0xDF, 0xDF, 0xC7, 0xD9, 0xD9, 0x19, 0x6C, 0x36, 0x1B,
0x36, 0x36, 0x36, 0x38, 0x9F, 0x85, 0x85, 0x05, 0xAC, 0xAF, 0xAF, 0x23, 0x1A, 0x8D, 0x22, 0x91,
0x48, 0x20, 0x16, 0x8B, 0xFD, 0xDA, 0xDA, 0xDA, 0x7A, 0x41, 0x33, 0x7E, 0x57, 0x50, 0x50, 0x80,
0x89, 0x89, 0x09, 0x84, 0xC3, 0x61, 0x6C, 0x6F, 0x6F, 0x23, 0x12, 0x89, 0xE0, 0xE0, 0xE0, 0x00,
0x43, 0x43, 0x43, 0x58, 0x5E, 0x5E, 0xE6, 0x9C, 0x7D, 0x3E, 0x1F, 0x46, 0x47, 0x47, 0x79, 0xBE,
0xBD, 0xBD, 0x3D, 0xE1, 0x3C, 0x1D, 0x0C, 0x06, 0x9F, 0x10, 0xB7, 0xC7, 0x84, 0x4F, 0xF6, 0xF7,
0xF7, 0x63, 0x60, 0x60, 0x00, 0x83, 0x83, 0x83, 0x18, 0x19, 0x19, 0xC1, 0xDC, 0xDC, 0x1C, 0x8F,
0x17, 0x7C, 0xA4, 0x27, 0xE7, 0x34, 0x39, 0x39, 0x89, 0x9D, 0x9D, 0x1D, 0x6E, 0x54, 0xE3, 0x13,
0xE5, 0x34, 0x11, 0x37, 0x49, 0x51, 0x51, 0xD1, 0x4B, 0xA5, 0x52, 0xF9, 0x45, 0x26, 0x93, 0x5D,
0x0A, 0xF3, 0x92, 0x48, 0x24, 0xA0, 0x6F, 0x14, 0x17, 0x17, 0xA3, 0xB6, 0xB6, 0x16, 0x5D, 0x5D,
0x5D, 0x7C, 0x1E, 0xBB, 0xBB, 0xBB, 0x9C, 0xD7, 0xE1, 0xE1, 0x21, 0x42, 0xA1, 0xD0, 0x6B, 0xD2,
0x45, 0x4C, 0x33, 0x12, 0x34, 0xCC, 0xA0, 0x19, 0x54, 0x92, 0x56, 0x0E, 0xD2, 0xD9, 0x43, 0xF8,
0xCF, 0x82, 0x56, 0xC2, 0xDC, 0xEB, 0xEA, 0xEA, 0x38, 0x7E, 0x6C, 0x6C, 0x4C, 0xE0, 0xFE, 0x9D,
0xB8, 0xBF, 0xA7, 0xFA, 0xAF, 0x56, 0x56, 0x56, 0xEE, 0x6D, 0x6E, 0x6E, 0xDE, 0xB8, 0x47, 0x55,
0x55, 0x55, 0x6C, 0x66, 0x66, 0x46, 0x44, 0xDA, 0x3B, 0x34, 0x1A, 0x4D, 0x94, 0xB0, 0x3F, 0x09,
0x7B, 0x45, 0xBD, 0xA5, 0x5D, 0x2E, 0x57, 0x8C, 0x7A, 0x73, 0xD9, 0xED, 0xF6, 0x3B, 0x84, 0xFF,
0xE7, 0x7D, 0xA6, 0x3A, 0x2C, 0x95, 0x4A, 0xB1, 0x8E, 0x8E, 0x0E, 0x6D, 0x77, 0x77, 0xB7, 0xCD,
0xE9, 0x74, 0x3E, 0x73, 0xBB, 0xDD, 0x8F, 0x3C, 0x1E, 0x8F, 0xE6, 0xF4, 0xF4, 0x94, 0xAD, 0xAD,
0xAD, 0xDD, 0xDE, 0xCF, 0x73, 0x0B, 0x0B, 0xB8, 0xB6, 0xE0, 0x5D, 0xC6, 0x66, 0xC5, 0xE4, 0x10,
0x4C, 0xF4, 0xF7, 0xD8, 0x59, 0xF2, 0x7F, 0xA3, 0xB8, 0xB4, 0xFC, 0x0F, 0xEE, 0x37, 0x70, 0xEC,
0x16, 0x4A, 0x7E, 0x04, 0x00, 0x00
};
AsyncWebServerResponse *response = request->beginResponse_P(200, "image/x-icon", favicon_ico_gz, favicon_ico_gz_len);
response->addHeader("Content-Encoding", "gzip");
request->send(response);
```
### Respond with content coming from a Stream
```cpp
//read 12 bytes from Serial and send them as Content Type text/plain
request->send(Serial, "text/plain", 12);
```
### Respond with content coming from a Stream and extra headers
```cpp
//read 12 bytes from Serial and send them as Content Type text/plain
AsyncWebServerResponse *response = request->beginResponse(Serial, "text/plain", 12);
response->addHeader("Server","ESP Async Web Server");
request->send(response);
```
### Respond with content coming from a Stream containing templates
```cpp
String processor(const String& var)
{
if(var == "HELLO_FROM_TEMPLATE")
return F("Hello world!");
return String();
}
// ...
//read 12 bytes from Serial and send them as Content Type text/plain
request->send(Serial, "text/plain", 12, processor);
```
### Respond with content coming from a Stream containing templates and extra headers
```cpp
String processor(const String& var)
{
if(var == "HELLO_FROM_TEMPLATE")
return F("Hello world!");
return String();
}
// ...
//read 12 bytes from Serial and send them as Content Type text/plain
AsyncWebServerResponse *response = request->beginResponse(Serial, "text/plain", 12, processor);
response->addHeader("Server","ESP Async Web Server");
request->send(response);
```
### Respond with content coming from a File
```cpp
//Send index.htm with default content type from SPIFFS
request->send(SPIFFS, "/index.htm");
//Send index.htm as text from SPIFFS
request->send(SPIFFS, "/index.htm", "text/plain");
//Download index.htm from SPIFFS
request->send(SPIFFS, "/index.htm", String(), true);
//Send index.htm with default content type from LittleFS
request->send(LittleFS, "/index.htm");
//Send index.htm as text from LittleFS
request->send(LittleFS, "/index.htm", "text/plain");
//Download index.htm from LittleFS
request->send(LittleFS, "/index.htm", String(), true);
```
See the [StaticFile example here](https://github.com/ESP32Async/ESPAsyncWebServer/blob/master/examples/StaticFile/StaticFile.ino).
### Respond with content coming from a File and extra headers
```cpp
//Send index.htm with default content type from SPIFFS
AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/index.htm");
//Send index.htm as text from SPIFFS
AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/index.htm", "text/plain");
//Download index.htm from SPIFFS
AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/index.htm", String(), true);
//Send index.htm with default content type from LittleFS
AsyncWebServerResponse *response = request->beginResponse(LittleFS, "/index.htm");
//Send index.htm as text from LittleFS
AsyncWebServerResponse *response = request->beginResponse(LittleFS, "/index.htm", "text/plain");
//Download index.htm from LittleFS
AsyncWebServerResponse *response = request->beginResponse(LittleFS, "/index.htm", String(), true);
response->addHeader("Server","ESP Async Web Server");
request->send(response);
```
### Respond with content coming from a File containing templates
Internally uses [Chunked Response](#chunked-response).
Index.htm contents:
```
%HELLO_FROM_TEMPLATE%
```
Somewhere in source files:
```cpp
String processor(const String& var)
{
if(var == "HELLO_FROM_TEMPLATE")
return F("Hello world!");
return String();
}
// ...
//Send index.htm with template processor function from SPIFFS
request->send(SPIFFS, "/index.htm", String(), false, processor);
//Send index.htm with template processor function from LittleFS
request->send(LittleFS, "/index.htm", String(), false, processor);
```
### Respond with content using a callback
```cpp
//send 128 bytes as plain text
request->send("text/plain", 128, [](uint8_t *buffer, size_t maxLen, size_t index) -> size_t {
//Write up to "maxLen" bytes into "buffer" and return the amount written.
//index equals the amount of bytes that have been already sent
//You will not be asked for more bytes once the content length has been reached.
//Keep in mind that you can not delay or yield waiting for more data!
//Send what you currently have and you will be asked for more again
return mySource.read(buffer, maxLen);
});
```
See the [ChunkResponse example here](https://github.com/ESP32Async/ESPAsyncWebServer/blob/master/examples/ChunkResponse/ChunkResponse.ino).
### Respond with content using a callback and extra headers
```cpp
//send 128 bytes as plain text
AsyncWebServerResponse *response = request->beginResponse("text/plain", 128, [](uint8_t *buffer, size_t maxLen, size_t index) -> size_t {
//Write up to "maxLen" bytes into "buffer" and return the amount written.
//index equals the amount of bytes that have been already sent
//You will not be asked for more bytes once the content length has been reached.
//Keep in mind that you can not delay or yield waiting for more data!
//Send what you currently have and you will be asked for more again
return mySource.read(buffer, maxLen);
});
response->addHeader("Server","ESP Async Web Server");
request->send(response);
```
### Respond with file content using a callback and extra headers
With this code your ESP is able to serve even large (large in terms of ESP, e.g. 100kB) files
without memory problems.
You need to create a file handler in outer function (to have a single one for request) but use
it in a lambda. The catch is that the lambda has it's own lifecycle which may/will cause it's
called after the original function is over thus the original file handle is destroyed. Using the
captured `&file` in the lambda then causes segfault (Hello, Exception 9!) and the whole ESP crashes.
By using this code, you tell the compiler to move the handle into the lambda so it won't be
destroyed when outer function (that one where you call `request->send(response)`) ends.
```cpp
// Example with SPIFFS
const File file = SPIFFS.open(path, "r");
const contentType = "application/javascript";
AsyncWebServerResponse *response = request->beginResponse(
contentType,
file.size(),
[file](uint8_t *buffer, size_t maxLen, size_t total) mutable -> size_t {
int bytes = file.read(buffer, maxLen);
// close file at the end
if (bytes + total == file.size()) file.close();
return max(0, bytes); // return 0 even when no bytes were loaded
}
);
if (gzipped) {
response->addHeader(F("Content-Encoding"), F("gzip"));
}
request->send(response);
```
```cpp
// Example with LittleFS
const File file = LittleFS.open(path, "r");
const contentType = "application/javascript";
AsyncWebServerResponse *response = request->beginResponse(
contentType,
file.size(),
[file](uint8_t *buffer, size_t maxLen, size_t total) mutable -> size_t {
int bytes = file.read(buffer, maxLen);
// close file at the end
if (bytes + total == file.size()) file.close();
return max(0, bytes); // return 0 even when no bytes were loaded
}
);
if (gzipped) {
response->addHeader(F("Content-Encoding"), F("gzip"));
}
request->send(response);
```
### Respond with content using a callback containing templates
```cpp
String processor(const String& var)
{
if(var == "HELLO_FROM_TEMPLATE")
return F("Hello world!");
return String();
}
// ...
//send 128 bytes as plain text
request->send("text/plain", 128, [](uint8_t *buffer, size_t maxLen, size_t index) -> size_t {
//Write up to "maxLen" bytes into "buffer" and return the amount written.
//index equals the amount of bytes that have been already sent
//You will not be asked for more bytes once the content length has been reached.
//Keep in mind that you can not delay or yield waiting for more data!
//Send what you currently have and you will be asked for more again
return mySource.read(buffer, maxLen);
}, processor);
```
### Respond with content using a callback containing templates and extra headers
```cpp
String processor(const String& var)
{
if(var == "HELLO_FROM_TEMPLATE")
return F("Hello world!");
return String();
}
// ...
//send 128 bytes as plain text
AsyncWebServerResponse *response = request->beginResponse("text/plain", 128, [](uint8_t *buffer, size_t maxLen, size_t index) -> size_t {
//Write up to "maxLen" bytes into "buffer" and return the amount written.
//index equals the amount of bytes that have been already sent
//You will not be asked for more bytes once the content length has been reached.
//Keep in mind that you can not delay or yield waiting for more data!
//Send what you currently have and you will be asked for more again
return mySource.read(buffer, maxLen);
}, processor);
response->addHeader("Server","ESP Async Web Server");
request->send(response);
```
### Chunked Response
Used when content length is unknown. Works best if the client supports HTTP/1.1
```cpp
AsyncWebServerResponse *response = request->beginChunkedResponse("text/plain", [](uint8_t *buffer, size_t maxLen, size_t index) -> size_t {
//Write up to "maxLen" bytes into "buffer" and return the amount written.
//index equals the amount of bytes that have been already sent
//You will be asked for more data until 0 is returned
//Keep in mind that you can not delay or yield waiting for more data!
return mySource.read(buffer, maxLen);
});
response->addHeader("Server","ESP Async Web Server");
request->send(response);
```
See the [ChunkRequest example here](https://github.com/ESP32Async/ESPAsyncWebServer/blob/master/examples/ChunkRequest/ChunkRequest.ino) and [ChunkRetryResponse example here](https://github.com/ESP32Async/ESPAsyncWebServer/blob/master/examples/ChunkRetryResponse/ChunkRetryResponse.ino).
### Chunked Response containing templates
Used when content length is unknown. Works best if the client supports HTTP/1.1
```cpp
String processor(const String& var)
{
if(var == "HELLO_FROM_TEMPLATE")
return F("Hello world!");
return String();
}
// ...
AsyncWebServerResponse *response = request->beginChunkedResponse("text/plain", [](uint8_t *buffer, size_t maxLen, size_t index) -> size_t {
//Write up to "maxLen" bytes into "buffer" and return the amount written.
//index equals the amount of bytes that have been already sent
//You will be asked for more data until 0 is returned
//Keep in mind that you can not delay or yield waiting for more data!
return mySource.read(buffer, maxLen);
}, processor);
response->addHeader("Server","ESP Async Web Server");
request->send(response);
```
### Print to response
```cpp
AsyncResponseStream *response = request->beginResponseStream("text/html");
response->addHeader("Server","ESP Async Web Server");
response->printf("<!DOCTYPE html><html><head><title>Webpage at %s</title></head><body>", request->url().c_str());
response->print("<h2>Hello ");
response->print(request->client()->remoteIP());
response->print("</h2>");
response->print("<h3>General</h3>");
response->print("<ul>");
response->printf("<li>Version: HTTP/1.%u</li>", request->version());
response->printf("<li>Method: %s</li>", request->methodToString());
response->printf("<li>URL: %s</li>", request->url().c_str());
response->printf("<li>Host: %s</li>", request->host().c_str());
response->printf("<li>ContentType: %s</li>", request->contentType().c_str());
response->printf("<li>ContentLength: %u</li>", request->contentLength());
response->printf("<li>Multipart: %s</li>", request->multipart()?"true":"false");
response->print("</ul>");
response->print("<h3>Headers</h3>");
response->print("<ul>");
int headers = request->headers();
for(int i=0;i<headers;i++){
AsyncWebHeader* h = request->getHeader(i);
response->printf("<li>%s: %s</li>", h->name().c_str(), h->value().c_str());
}
response->print("</ul>");
response->print("<h3>Parameters</h3>");
response->print("<ul>");
int params = request->params();
for(int i=0;i<params;i++){
AsyncWebParameter* p = request->getParam(i);
if(p->isFile()){
response->printf("<li>FILE[%s]: %s, size: %u</li>", p->name().c_str(), p->value().c_str(), p->size());
} else if(p->isPost()){
response->printf("<li>POST[%s]: %s</li>", p->name().c_str(), p->value().c_str());
} else {
response->printf("<li>GET[%s]: %s</li>", p->name().c_str(), p->value().c_str());
}
}
response->print("</ul>");
response->print("</body></html>");
//send the response last
request->send(response);
```
See the [AsyncResponseStream example here](https://github.com/ESP32Async/ESPAsyncWebServer/blob/master/examples/AsyncResponseStream/AsyncResponseStream.ino).
### ArduinoJson Basic Response
This way of sending Json is great for when the result is below 4KB
```cpp
#include "AsyncJson.h"
#include "ArduinoJson.h"
AsyncResponseStream *response = request->beginResponseStream("application/json");
JsonDocument doc;
doc["heap"] = ESP.getFreeHeap();
doc["ssid"] = WiFi.SSID();
serializeJson(doc, *response);
request->send(response);
```
### ArduinoJson Advanced Response
This response can handle really large Json objects (tested to 40KB)
There isn't any noticeable speed decrease for small results with the method above
Since ArduinoJson does not allow reading parts of the string, the whole Json has to
be passed every time a chunks needs to be sent, which shows speed decrease proportional
to the resulting json packets
```cpp
#include "AsyncJson.h"
#include "ArduinoJson.h"
AsyncJsonResponse * response = new AsyncJsonResponse();
response->addHeader("Server","ESP Async Web Server");
JsonDocument& doc = response->getRoot();
doc["heap"] = ESP.getFreeHeap();
doc["ssid"] = WiFi.SSID();
response->setLength();
request->send(response);
```
See the [Json example here](https://github.com/ESP32Async/ESPAsyncWebServer/blob/master/examples/Json/Json.ino).
### MessagePack Response
MessagePack is a binary serialization format that is more compact than JSON.
```cpp
#include "AsyncMessagePack.h"
#include "ArduinoJson.h"
AsyncMessagePackResponse * response = new AsyncMessagePackResponse();
response->addHeader("Server","ESP Async Web Server");
JsonDocument& doc = response->getRoot();
doc["heap"] = ESP.getFreeHeap();
doc["ssid"] = WiFi.SSID();
response->setLength();
request->send(response);
```
See the [MessagePack example here](https://github.com/ESP32Async/ESPAsyncWebServer/blob/master/examples/MessagePack/MessagePack.ino).
## Adding Default Headers
In some cases, such as when working with CORS, or with some sort of custom authentication system,
you might need to define a header that should get added to all responses (including static, websocket and EventSource).
The DefaultHeaders singleton allows you to do this.
Example:
```cpp
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*");
webServer.begin();
```
_NOTE_: You will still need to respond to the OPTIONS method for CORS pre-flight in most cases. (unless you are only using GET)
This is one option:
```cpp
webServer.onNotFound([](AsyncWebServerRequest *request) {
if (request->method() == HTTP_OPTIONS) {
request->send(200);
} else {
request->send(404);
}
});
```
See the [CORS example here](https://github.com/ESP32Async/ESPAsyncWebServer/blob/master/examples/CORS/CORS.ino).
## Bad Responses
Some responses are implemented, but you should not use them, because they do not conform to HTTP.
The following example will lead to unclean close of the connection and more time wasted
than providing the length of the content
### Respond with content using a callback without content length to HTTP/1.0 clients
```cpp
//This is used as fallback for chunked responses to HTTP/1.0 Clients
request->send("text/plain", 0, [](uint8_t *buffer, size_t maxLen, size_t index) -> size_t {
//Write up to "maxLen" bytes into "buffer" and return the amount written.
//You will be asked for more data until 0 is returned
//Keep in mind that you can not delay or yield waiting for more data!
return mySource.read(buffer, maxLen);
});
```

View File

@@ -1,151 +0,0 @@
# Routing
## URL Matching and Routing
The `AsyncURIMatcher` class provides flexible and powerful URL routing mechanisms.
**Important**: When using plain strings (not `AsyncURIMatcher` objects), the library uses auto-detection (`URIMatchAuto`) which analyzes the URI pattern and applies appropriate matching rules. This is **not** simple exact matching - it combines exact and folder matching by default!
### Auto-Detection Behavior
When you pass a plain string or `const char*` to `server.on()`, the `URIMatchAuto` flag is used, which:
1. **Empty URI**: Matches everything
2. **Ends with `*`**: Becomes prefix match (`URIMatchPrefix`)
3. **Contains `/*.ext`**: Becomes extension match (`URIMatchExtension`)
4. **Starts with `^` and ends with `$`**: Becomes regex match (if regex enabled)
5. **Everything else**: Becomes **both** exact and folder match (`URIMatchPrefixFolder | URIMatchExact`)
This means traditional string-based routes like `server.on("/path", handler)` will match:
- `/path` (exact match)
- `/path/` (folder with trailing slash)
- `/path/anything` (folder match)
But will **NOT** match `/path-suffix` (prefix without folder separator).
### Usage Patterns
#### Traditional String-based Routing (Auto-Detection)
```cpp
// Auto-detection with exact + folder matching
server.on("/api", handler); // Matches /api AND /api/anything
server.on("/login", handler); // Matches /login AND /login/sub
// Auto-detection with prefix matching
server.on("/prefix*", handler); // Matches /prefix, /prefix-test, /prefix/sub
// Auto-detection with extension matching
server.on("/images/*.jpg", handler); // Matches /images/pic.jpg, /images/sub/pic.jpg
```
#### Factory Functions
Using factory functions is clearer and gives you explicit control over matching behavior.
```cpp
// Exact matching only (matches "/login" but NOT "/login/sub")
server.on(AsyncURIMatcher::exact("/login"), handler);
// Prefix matching (matches "/api", "/api/v1", "/api-test")
server.on(AsyncURIMatcher::prefix("/api"), handler);
// Directory matching (matches "/admin/users" but NOT "/admin")
// Requires trailing slash in URL conceptually
server.on(AsyncURIMatcher::dir("/admin"), handler);
// Extension matching
server.on(AsyncURIMatcher::ext("/images/*.jpg"), handler);
// Case insensitive matching
server.on(AsyncURIMatcher::exact("/case", AsyncURIMatcher::CaseInsensitive), handler);
// matches "/case", "/CASE", "/CaSe"
// Regular Expression (requires -D ASYNCWEBSERVER_REGEX)
#ifdef ASYNCWEBSERVER_REGEX
server.on(AsyncURIMatcher::regex("^/user/([0-9]+)$"), handler);
#endif
```
## Param Rewrite With Matching
It is possible to rewrite the request url with parameter match. Here is an example with one parameter:
Rewrite for example "/radio/{frequence}" -> "/radio?f={frequence}"
```cpp
class OneParamRewrite : public AsyncWebRewrite
{
protected:
String _urlPrefix;
int _paramIndex;
String _paramsBackup;
public:
OneParamRewrite(const char* from, const char* to)
: AsyncWebRewrite(from, to) {
_paramIndex = _from.indexOf('{');
if( _paramIndex >=0 && _from.endsWith("}")) {
_urlPrefix = _from.substring(0, _paramIndex);
int index = _params.indexOf('{');
if(index >= 0) {
_params = _params.substring(0, index);
}
} else {
_urlPrefix = _from;
}
_paramsBackup = _params;
}
bool match(AsyncWebServerRequest *request) override {
if(request->url().startsWith(_urlPrefix)) {
if(_paramIndex >= 0) {
_params = _paramsBackup + request->url().substring(_paramIndex);
} else {
_params = _paramsBackup;
}
return true;
} else {
return false;
}
}
};
```
Usage:
```cpp
server.addRewrite( new OneParamRewrite("/radio/{frequence}", "/radio?f={frequence}") );
```
See the [Rewrite example here](https://github.com/ESP32Async/ESPAsyncWebServer/blob/master/examples/Rewrite/Rewrite.ino).
## Remove handlers and rewrites
Server goes through handlers in same order as they were added. You can't simple add handler with same path to override them.
To remove handler:
```arduino
// save callback for particular URL path
auto& handler = server.on("/some/path", [](AsyncWebServerRequest *request){
//do something useful
});
// when you don't need handler anymore remove it
server.removeHandler(&handler);
// same with rewrites
server.removeRewrite(&someRewrite);
server.onNotFound([](AsyncWebServerRequest *request){
request->send(404);
});
// remove server.onNotFound handler
server.onNotFound(NULL);
// remove all rewrites, handlers and onNotFound/onFileUpload/onRequestBody callbacks
server.reset();
```

View File

@@ -1,197 +0,0 @@
# Server Setup
## Setting up the server
```cpp
#include <Arduino.h>
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
AsyncWebServer server(80);
AsyncWebSocket ws("/ws"); // access at ws://[esp ip]/ws
AsyncEventSource events("/events"); // event source (Server-Sent events)
const char* ssid = "your-ssid";
const char* password = "your-pass";
//flag to use from web update to reboot the ESP
bool shouldReboot = false;
void onRequest(AsyncWebServerRequest *request){
//Handle Unknown Request
request->send(404);
}
void onBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total){
//Handle body
}
void onUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final){
//Handle upload
}
void onEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){
//Handle WebSocket event
}
void setup(){
Serial.begin(115200);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
if (WiFi.waitForConnectResult() != WL_CONNECTED) {
Serial.printf("WiFi Failed!\n");
return;
}
// attach AsyncWebSocket
ws.onEvent(onEvent);
server.addHandler(&ws);
// attach AsyncEventSource
server.addHandler(&events);
// respond to GET requests on URL /heap
server.on("/heap", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(200, "text/plain", String(ESP.getFreeHeap()));
});
// upload a file to /upload
server.on("/upload", HTTP_POST, [](AsyncWebServerRequest *request){
request->send(200);
}, onUpload);
// send a file when /index is requested (SPIFFS example)
server.on("/index", HTTP_ANY, [](AsyncWebServerRequest *request){
request->send(SPIFFS, "/index.htm");
});
// send a file when /index is requested (LittleFS example)
server.on("/index", HTTP_ANY, [](AsyncWebServerRequest *request){
request->send(LittleFS, "/index.htm");
});
// Simple Firmware Update Form
server.on("/update", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(200, "text/html", "<form method='POST' action='/update' enctype='multipart/form-data'><input type='file' name='update'><input type='submit' value='Update'></form>");
});
server.on("/update", HTTP_POST, [](AsyncWebServerRequest *request){
shouldReboot = !Update.hasError();
AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", shouldReboot?"OK":"FAIL");
response->addHeader("Connection", "close");
request->send(response);
},[](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final){
if(!index){
Serial.printf("Update Start: %s\n", filename.c_str());
Update.runAsync(true);
if(!Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000)){
Update.printError(Serial);
}
}
if(!Update.hasError()){
if(Update.write(data, len) != len){
Update.printError(Serial);
}
}
if(final){
if(Update.end(true)){
Serial.printf("Update Success: %uB\n", index+len);
} else {
Update.printError(Serial);
}
}
});
// attach filesystem root at URL /fs (SPIFFS example)
server.serveStatic("/fs", SPIFFS, "/");
// attach filesystem root at URL /fs (LittleFS example)
server.serveStatic("/fs", LittleFS, "/");
// Catch-All Handlers
// Any request that can not find a Handler that canHandle it
// ends in the callbacks below.
server.onNotFound(onRequest);
server.onFileUpload(onUpload);
server.onRequestBody(onBody);
server.begin();
}
void loop(){
if(shouldReboot){
Serial.println("Rebooting...");
delay(100);
ESP.restart();
}
static char temp[128];
sprintf(temp, "Seconds since boot: %u", millis()/1000);
events.send(temp, "time"); //send event "time"
}
```
**IMPORTANT**: Authentication should now use `AsyncAuthenticationMiddleware` instead of the deprecated methods. See the [Authentication with AsyncAuthenticationMiddleware](middleware.md#authentication-with-asyncauthenticationmiddleware) section.
### Setup global and class functions as request handlers
```cpp
#include <Arduino.h>
#include <ESPAsyncWebserver.h>
#include <Hash.h>
#include <functional>
void handleRequest(AsyncWebServerRequest *request){}
class WebClass {
public :
AsyncWebServer classWebServer = AsyncWebServer(81);
WebClass(){};
void classRequest (AsyncWebServerRequest *request){}
void begin(){
// attach global request handler
classWebServer.on("/example", HTTP_ANY, handleRequest);
// attach class request handler
classWebServer.on("/example", HTTP_ANY, std::bind(&WebClass::classRequest, this, std::placeholders::_1));
}
};
AsyncWebServer globalWebServer(80);
WebClass webClassInstance;
void setup() {
// attach global request handler
globalWebServer.on("/example", HTTP_ANY, handleRequest);
// attach class request handler
globalWebServer.on("/example", HTTP_ANY, std::bind(&WebClass::classRequest, webClassInstance, std::placeholders::_1));
}
void loop() {
}
```
### Methods for controlling websocket connections
```cpp
// Disable client connections if it was activated
if ( ws.enabled() )
ws.enable(false);
// enable client connections if it was disabled
if ( !ws.enabled() )
ws.enable(true);
```

View File

@@ -1,210 +0,0 @@
# Static Files
## Serving static files
In addition to serving files from SPIFFS / LittleFS as described above, the server provide a dedicated handler that optimize the
performance of serving files from SPIFFS / LittleFS - `AsyncStaticWebHandler`. Use `server.serveStatic()` function to
initialize and add a new instance of `AsyncStaticWebHandler` to the server.
The Handler will not handle the request if the file does not exists, e.g. the server will continue to look for another
handler that can handle the request.
Notice that you can chain setter functions to setup the handler, or keep a pointer to change it at a later time.
### Serving specific file by name
```cpp
// Serve the file "/www/page.htm" when request url is "/page.htm" from SPIFFS
server.serveStatic("/page.htm", SPIFFS, "/www/page.htm");
// Serve the file "/www/page.htm" when request url is "/page.htm" from LittleFS
server.serveStatic("/page.htm", LittleFS, "/www/page.htm");
```
### Serving files in directory
To serve files in a directory, the path to the files should specify a directory in SPIFFS / LittleFS and ends with "/".
```cpp
// Serve files in directory "/www/" when request url starts with "/" from SPIFFS
// Request to the root or none existing files will try to server the default
// file name "index.htm" if exists
server.serveStatic("/", SPIFFS, "/www/");
// Server with different default file from SPIFFS
server.serveStatic("/", SPIFFS, "/www/").setDefaultFile("default.html");
// Serve files in directory "/www/" when request url starts with "/" from LittleFS
// Request to the root or none existing files will try to server the default
// file name "index.htm" if exists
server.serveStatic("/", LittleFS, "/www/");
// Server with different default file from LittleFS
server.serveStatic("/", LittleFS, "/www/").setDefaultFile("default.html");
```
### Serving static files with authentication
**IMPORTANT**: Use `AsyncAuthenticationMiddleware` instead of the deprecated `setAuthentication()` method.
```cpp
AsyncAuthenticationMiddleware authMiddleware;
authMiddleware.setAuthType(AsyncAuthType::AUTH_DIGEST);
authMiddleware.setRealm("My app name");
authMiddleware.setUsername("admin");
authMiddleware.setPassword("admin");
authMiddleware.generateHash();
// For SPIFFS
server
.serveStatic("/", SPIFFS, "/www/")
.setDefaultFile("default.html")
.addMiddleware(&authMiddleware);
// For LittleFS
server
.serveStatic("/", LittleFS, "/www/")
.setDefaultFile("default.html")
.addMiddleware(&authMiddleware);
```
### Specifying Cache-Control header
It is possible to specify Cache-Control header value to reduce the number of calls to the server once the client loaded
the files. For more information on Cache-Control values see [Cache-Control](https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9)
```cpp
// Cache responses for 10 minutes (600 seconds) from SPIFFS
server.serveStatic("/", SPIFFS, "/www/").setCacheControl("max-age=600");
// Cache responses for 10 minutes (600 seconds) from LittleFS
server.serveStatic("/", LittleFS, "/www/").setCacheControl("max-age=600");
//*** Change Cache-Control after server setup ***
// During setup - keep a pointer to the handler (SPIFFS)
AsyncStaticWebHandler* handler = &server.serveStatic("/", SPIFFS, "/www/").setCacheControl("max-age=600");
// During setup - keep a pointer to the handler (LittleFS)
AsyncStaticWebHandler* handler = &server.serveStatic("/", LittleFS, "/www/").setCacheControl("max-age=600");
// At a later event - change Cache-Control
handler->setCacheControl("max-age=30");
```
### Specifying Date-Modified header
It is possible to specify Date-Modified header to enable the server to return Not-Modified (304) response for requests
with "If-Modified-Since" header with the same value, instead of responding with the actual file content.
```cpp
// Update the date modified string every time files are updated (SPIFFS)
server.serveStatic("/", SPIFFS, "/www/").setLastModified("Mon, 20 Jun 2016 14:00:00 GMT");
// Update the date modified string every time files are updated (LittleFS)
server.serveStatic("/", LittleFS, "/www/").setLastModified("Mon, 20 Jun 2016 14:00:00 GMT");
//*** Change last modified value at a later stage ***
// During setup - read last modified value from config or EEPROM (SPIFFS)
String date_modified = loadDateModified();
AsyncStaticWebHandler* handler = &server.serveStatic("/", SPIFFS, "/www/");
handler->setLastModified(date_modified);
// During setup - read last modified value from config or EEPROM (LittleFS)
String date_modified = loadDateModified();
AsyncStaticWebHandler* handler = &server.serveStatic("/", LittleFS, "/www/");
handler->setLastModified(date_modified);
// At a later event when files are updated
String date_modified = getNewDateModfied();
saveDateModified(date_modified); // Save for next reset
handler->setLastModified(date_modified);
```
### Specifying Template Processor callback
It is possible to specify template processor for static files. For information on template processor see
[Respond with content coming from a File containing templates](responses.md#respond-with-content-coming-from-a-file-containing-templates).
```cpp
String processor(const String& var)
{
if(var == "HELLO_FROM_TEMPLATE")
return F("Hello world!");
return String();
}
// ...
// For SPIFFS
server.serveStatic("/", SPIFFS, "/www/").setTemplateProcessor(processor);
// For LittleFS
server.serveStatic("/", LittleFS, "/www/").setTemplateProcessor(processor);
```
### Serving static files by custom handling
It may happen your static files are too big and the ESP will crash the request before it sends the whole file.
In that case, you can handle static files with custom file serving through not found handler.
This code below is more-or-less equivalent to this:
```cpp
webServer.serveStatic("/", SPIFFS, STATIC_FILES_PREFIX).setDefaultFile("index.html")
// or
webServer.serveStatic("/", LittleFS, STATIC_FILES_PREFIX).setDefaultFile("index.html")
```
First, declare the handling function:
```cpp
bool handleStaticFile(AsyncWebServerRequest *request) {
String path = STATIC_FILES_PREFIX + request->url();
if (path.endsWith("/")) path += F("index.html");
String contentType = getContentType(path);
String pathWithGz = path + ".gz";
// Try SPIFFS first
if (SPIFFS.exists(pathWithGz) || SPIFFS.exists(path)) {
bool gzipped = false;
if (SPIFFS.exists(pathWithGz)) {
gzipped = true;
path += ".gz";
}
// TODO serve the file
return true;
}
// Try LittleFS if SPIFFS fails
if (LittleFS.exists(pathWithGz) || LittleFS.exists(path)) {
bool gzipped = false;
if (LittleFS.exists(pathWithGz)) {
gzipped = true;
path += ".gz";
}
// TODO serve the file
return true;
}
return false;
}
```
And then configure your webserver:
```cpp
webServer.onNotFound([](AsyncWebServerRequest *request) {
if (handleStaticFile(request)) return;
request->send(404);
});
```
You may want to try [Respond with file content using a callback and extra headers](responses.md#respond-with-file-content-using-a-callback-and-extra-headers)
For actual serving the file.

View File

@@ -1,208 +0,0 @@
# WebSockets
## `AsyncWebSocketMessageBuffer` and `makeBuffer()`
The fork from [yubox-node-org](https://github.com/yubox-node-org/ESPAsyncWebServer) introduces some breaking API changes compared to the original library, especially regarding the use of `std::shared_ptr<std::vector<uint8_t>>` for WebSocket.
This library is compatible with the original library from [me-no-dev](https://github.com/me-no-dev/ESPAsyncWebServer) regarding WebSocket, and wraps the optimizations done by `yubox-node-org` in the `AsyncWebSocketMessageBuffer` class.
So you have the choice of which API to use.
Here are examples for serializing a Json document in a websocket message buffer:
```cpp
void send(JsonDocument& doc) {
const size_t len = measureJson(doc);
// original API from me-no-dev
AsyncWebSocketMessageBuffer* buffer = _ws->makeBuffer(len);
assert(buffer); // up to you to keep or remove this
serializeJson(doc, buffer->get(), len);
_ws->textAll(buffer);
}
```
```cpp
void send(JsonDocument& doc) {
const size_t len = measureJson(doc);
// this fork (originally from yubox-node-org), uses another API with shared pointer
auto buffer = std::make_shared<std::vector<uint8_t>>(len);
assert(buffer); // up to you to keep or remove this
serializeJson(doc, buffer->data(), len);
_ws->textAll(std::move(buffer));
}
```
I recommend to use the official API `AsyncWebSocketMessageBuffer` to retain further compatibility.
## Async WebSocket Plugin
The server includes a web socket plugin which lets you define different WebSocket locations to connect to
without starting another listening service or using different port
See the [WebSocket example here](https://github.com/ESP32Async/ESPAsyncWebServer/blob/master/examples/WebSocket/WebSocket.ino) and [WebSocketEasy example here](https://github.com/ESP32Async/ESPAsyncWebServer/blob/master/examples/WebSocketEasy/WebSocketEasy.ino).
### Async WebSocket Event
```cpp
void onEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){
if(type == WS_EVT_CONNECT){
//client connected
os_printf("ws[%s][%u] connect\n", server->url(), client->id());
client->printf("Hello Client %u :)", client->id());
client->ping();
} else if(type == WS_EVT_DISCONNECT){
//client disconnected
os_printf("ws[%s][%u] disconnect: %u\n", server->url(), client->id());
} else if(type == WS_EVT_ERROR){
//error was received from the other end
os_printf("ws[%s][%u] error(%u): %s\n", server->url(), client->id(), *((uint16_t*)arg), (char*)data);
} else if(type == WS_EVT_PONG){
//pong message was received (in response to a ping request maybe)
os_printf("ws[%s][%u] pong[%u]: %s\n", server->url(), client->id(), len, (len)?(char*)data:"");
} else if(type == WS_EVT_DATA){
//data packet
AwsFrameInfo * info = (AwsFrameInfo*)arg;
if(info->final && info->index == 0 && info->len == len){
//the whole message is in a single frame and we got all of it's data
os_printf("ws[%s][%u] %s-message[%llu]: ", server->url(), client->id(), (info->opcode == WS_TEXT)?"text":"binary", info->len);
if(info->opcode == WS_TEXT){
data[len] = 0;
os_printf("%s\n", (char*)data);
} else {
for(size_t i=0; i < info->len; i++){
os_printf("%02x ", data[i]);
}
os_printf("\n");
}
if(info->opcode == WS_TEXT)
client->text("I got your text message");
else
client->binary("I got your binary message");
} else {
//message is comprised of multiple frames or the frame is split into multiple packets
if(info->index == 0){
if(info->num == 0)
os_printf("ws[%s][%u] %s-message start\n", server->url(), client->id(), (info->message_opcode == WS_TEXT)?"text":"binary");
os_printf("ws[%s][%u] frame[%u] start[%llu]\n", server->url(), client->id(), info->num, info->len);
}
os_printf("ws[%s][%u] frame[%u] %s[%llu - %llu]: ", server->url(), client->id(), info->num, (info->message_opcode == WS_TEXT)?"text":"binary", info->index, info->index + len);
if(info->message_opcode == WS_TEXT){
data[len] = 0;
os_printf("%s\n", (char*)data);
} else {
for(size_t i=0; i < len; i++){
os_printf("%02x ", data[i]);
}
os_printf("\n");
}
if((info->index + len) == info->len){
os_printf("ws[%s][%u] frame[%u] end[%llu]\n", server->url(), client->id(), info->num, info->len);
if(info->final){
os_printf("ws[%s][%u] %s-message end\n", server->url(), client->id(), (info->message_opcode == WS_TEXT)?"text":"binary");
if(info->message_opcode == WS_TEXT)
client->text("I got your text message");
else
client->binary("I got your binary message");
}
}
}
}
}
```
### Methods for sending data to a socket client
```cpp
//Server methods
AsyncWebSocket ws("/ws");
//printf to a client
ws.printf((uint32_t)client_id, arguments...);
//printf to all clients
ws.printfAll(arguments...);
//printf_P to a client
ws.printf_P((uint32_t)client_id, PSTR(format), arguments...);
//printfAll_P to all clients
ws.printfAll_P(PSTR(format), arguments...);
//send text to a client
ws.text((uint32_t)client_id, (char*)text);
ws.text((uint32_t)client_id, (uint8_t*)text, (size_t)len);
//send text from PROGMEM to a client
ws.text((uint32_t)client_id, PSTR("text"));
const char flash_text[] PROGMEM = "Text to send"
ws.text((uint32_t)client_id, FPSTR(flash_text));
//send text to all clients
ws.textAll((char*)text);
ws.textAll((uint8_t*)text, (size_t)len);
//send binary to a client
ws.binary((uint32_t)client_id, (char*)binary);
ws.binary((uint32_t)client_id, (uint8_t*)binary, (size_t)len);
//send binary from PROGMEM to a client
const uint8_t flash_binary[] PROGMEM = { 0x01, 0x02, 0x03, 0x04 };
ws.binary((uint32_t)client_id, flash_binary, 4);
//send binary to all clients
ws.binaryAll((char*)binary);
ws.binaryAll((uint8_t*)binary, (size_t)len);
//client methods
AsyncWebSocketClient * client;
//printf
client->printf(arguments...);
//printf_P
client->printf_P(PSTR(format), arguments...);
//send text
client->text((char*)text);
client->text((uint8_t*)text, (size_t)len);
//send text from PROGMEM
client->text(PSTR("text"));
const char flash_text[] PROGMEM = "Text to send";
client->text(FPSTR(flash_text));
//send binary
client->binary((char*)binary);
client->binary((uint8_t*)binary, (size_t)len);
//send binary from PROGMEM
const uint8_t flash_binary[] PROGMEM = { 0x01, 0x02, 0x03, 0x04 };
client->binary(flash_binary, 4);
```
### Direct access to web socket message buffer
When sending a web socket message using the above methods a buffer is created. Under certain circumstances you might want to manipulate or populate this buffer directly from your application, for example to prevent unnecessary duplications of the data. This example below shows how to create a buffer and print data to it from an ArduinoJson object then send it.
```cpp
void sendDataWs(AsyncWebSocketClient * client)
{
JsonDocument doc;
doc["a"] = "abc";
doc["b"] = "abcd";
doc["c"] = "abcde";
doc["d"] = "abcdef";
doc["e"] = "abcdefg";
size_t len = measureJson(doc);
AsyncWebSocketMessageBuffer * buffer = ws.makeBuffer(len); // creates a buffer (len + 1) for you.
if (buffer) {
serializeJson(doc, (char *)buffer->get(), len + 1);
if (client) {
client->text(buffer);
} else {
ws.textAll(buffer);
}
}
}
```
### Limiting the number of web socket clients
Browsers sometimes do not correctly close the websocket connection, even when the close() function is called in javascript. This will eventually exhaust the web server's resources and will cause the server to crash. Periodically calling the cleanClients() function from the main loop() function limits the number of clients by closing the oldest client when the maximum number of clients has been exceeded. This can called be every cycle, however, if you wish to use less power, then calling as infrequently as once per second is sufficient.
```cpp
void loop(){
ws.cleanupClients();
}
```

View File

@@ -1,47 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2026 Hristo Gochkov, Mathieu Carbou, Emil Muratov, Will Miles
#include <DNSServer.h>
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
static AsyncWebServer server(80);
void setup() {
Serial.begin(115200);
#if ASYNCWEBSERVER_WIFI_SUPPORTED
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
// Shows how to use AsyncResponseStream.
// The internal buffer will be allocated and data appended to it,
// until the response is sent, then this buffer is read and committed on the network.
//
// curl -v http://192.168.4.1/
//
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
AsyncResponseStream *response = request->beginResponseStream("plain/text", 40 * 1024);
for (int i = 0; i < 32 * 1024; i++) {
response->write('a');
}
request->send(response);
});
server.begin();
}
void loop() {
delay(100);
}

View File

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

View File

@@ -1,219 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2026 Hristo Gochkov, Mathieu Carbou, Emil Muratov, Will Miles
//
// Authentication and authorization middlewares
//
#include <Arduino.h>
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
static AsyncWebServer server(80);
// basicAuth
static AsyncAuthenticationMiddleware basicAuth;
static AsyncAuthenticationMiddleware basicAuthHash;
// simple digest authentication
static AsyncAuthenticationMiddleware digestAuth;
static AsyncAuthenticationMiddleware digestAuthHash;
static AsyncAuthenticationMiddleware bearerAuthSharedKey;
static AsyncAuthenticationMiddleware bearerAuthJWT;
// complex authentication which adds request attributes for the next middlewares and handler
static AsyncMiddlewareFunction complexAuth([](AsyncWebServerRequest *request, ArMiddlewareNext next) {
if (request->authenticate("Mathieu", "password")) {
request->setAttribute("user", "Mathieu");
} else if (request->authenticate("Bob", "password")) {
request->setAttribute("user", "Bob");
} else {
return request->requestAuthentication();
}
if (request->getAttribute("user") == "Mathieu") {
request->setAttribute("role", "staff");
} else {
request->setAttribute("role", "user");
}
next();
});
static AsyncAuthorizationMiddleware authz([](AsyncWebServerRequest *request) {
return request->getAttribute("role") == "staff";
});
void setup() {
Serial.begin(115200);
#if ASYNCWEBSERVER_WIFI_SUPPORTED
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
// basic authentication
basicAuth.setUsername("admin");
basicAuth.setPassword("admin");
basicAuth.setRealm("MyApp");
basicAuth.setAuthFailureMessage("Authentication failed");
basicAuth.setAuthType(AsyncAuthType::AUTH_BASIC);
basicAuth.generateHash(); // precompute hash (optional but recommended)
// basic authentication with hash
basicAuthHash.setUsername("admin");
basicAuthHash.setPasswordHash("YWRtaW46YWRtaW4="); // BASE64(admin:admin)
basicAuthHash.setRealm("MyApp");
basicAuthHash.setAuthFailureMessage("Authentication failed");
basicAuthHash.setAuthType(AsyncAuthType::AUTH_BASIC);
// digest authentication
digestAuth.setUsername("admin");
digestAuth.setPassword("admin");
digestAuth.setRealm("MyApp");
digestAuth.setAuthFailureMessage("Authentication failed");
digestAuth.setAuthType(AsyncAuthType::AUTH_DIGEST);
digestAuth.generateHash(); // precompute hash (optional but recommended)
// digest authentication with hash
digestAuthHash.setUsername("admin");
digestAuthHash.setPasswordHash("f499b71f9a36d838b79268e145e132f7"); // MD5(user:realm:pass)
digestAuthHash.setRealm("MyApp");
digestAuthHash.setAuthFailureMessage("Authentication failed");
digestAuthHash.setAuthType(AsyncAuthType::AUTH_DIGEST);
// bearer authentication with shared key
bearerAuthSharedKey.setAuthType(AsyncAuthType::AUTH_BEARER);
bearerAuthSharedKey.setToken("shared-secret-key");
// bearer authentication with a JWT token
bearerAuthJWT.setAuthType(AsyncAuthType::AUTH_BEARER);
bearerAuthJWT.setAuthentificationFunction([](AsyncWebServerRequest *request) {
const String &token = request->authChallenge();
// 1. decode base64 token
// 2. decrypt token
const String &decrypted = "..."; // TODO
// 3. validate token (check signature, expiration, etc)
bool valid = token == "<token>" || token == "<another token>";
if (!valid) {
return false;
}
// 4. extract user info from token and set request attributes
if (token == "<token>") {
request->setAttribute("user", "Mathieu");
request->setAttribute("role", "staff");
return true; // return true if token is valid, false otherwise
}
if (token == "<another token>") {
request->setAttribute("user", "Bob");
request->setAttribute("role", "user");
return true; // return true if token is valid, false otherwise
}
return false;
});
// basic authentication method
// curl -v -u admin:admin http://192.168.4.1/auth-basic
server
.on(
"/auth-basic", HTTP_GET,
[](AsyncWebServerRequest *request) {
request->send(200, "text/plain", "Hello, world!");
}
)
.addMiddleware(&basicAuth);
// basic authentication method with hash
// curl -v -u admin:admin http://192.168.4.1/auth-basic-hash
server
.on(
"/auth-basic-hash", HTTP_GET,
[](AsyncWebServerRequest *request) {
request->send(200, "text/plain", "Hello, world!");
}
)
.addMiddleware(&basicAuthHash);
// digest authentication
// curl -v -u admin:admin --digest http://192.168.4.1/auth-digest
server
.on(
"/auth-digest", HTTP_GET,
[](AsyncWebServerRequest *request) {
request->send(200, "text/plain", "Hello, world!");
}
)
.addMiddleware(&digestAuth);
// digest authentication with hash
// curl -v -u admin:admin --digest http://192.168.4.1/auth-digest-hash
server
.on(
"/auth-digest-hash", HTTP_GET,
[](AsyncWebServerRequest *request) {
request->send(200, "text/plain", "Hello, world!");
}
)
.addMiddleware(&digestAuthHash);
// test digest auth custom authorization middleware
// curl -v --digest -u Mathieu:password http://192.168.4.1/auth-custom => OK
// curl -v --digest -u Bob:password http://192.168.4.1/auth-custom => 403
// curl -v --digest -u any:password http://192.168.4.1/auth-custom => 401
server
.on(
"/auth-custom", HTTP_GET,
[](AsyncWebServerRequest *request) {
String buffer = "Hello ";
buffer.concat(request->getAttribute("user"));
buffer.concat(" with role: ");
buffer.concat(request->getAttribute("role"));
request->send(200, "text/plain", buffer);
}
)
.addMiddlewares({&complexAuth, &authz});
// Bearer authentication with a shared key
// curl -v -H "Authorization: Bearer shared-secret-key" http://192.168.4.1/auth-bearer-shared-key => OK
server
.on(
"/auth-bearer-shared-key", HTTP_GET,
[](AsyncWebServerRequest *request) {
request->send(200, "text/plain", "Hello, world!");
}
)
.addMiddleware(&bearerAuthSharedKey);
// Bearer authentication with a JWT token
// curl -v -H "Authorization: Bearer <token>" http://192.168.4.1/auth-bearer-jwt => OK
// curl -v -H "Authorization: Bearer <another token>" http://192.168.4.1/auth-bearer-jwt => 403 Forbidden
// curl -v -H "Authorization: Bearer invalid-token" http://192.168.4.1/auth-bearer-jwt => 401 Unauthorized
server
.on(
"/auth-bearer-jwt", HTTP_GET,
[](AsyncWebServerRequest *request) {
Serial.println("User: " + request->getAttribute("user"));
Serial.println("Role: " + request->getAttribute("role"));
request->send(200, "text/plain", "Hello, world!");
}
)
.addMiddlewares({&bearerAuthJWT, &authz});
server.begin();
}
// not needed
void loop() {
delay(100);
}

View File

@@ -1,60 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2026 Hristo Gochkov, Mathieu Carbou, Emil Muratov, Will Miles
//
// How to use CORS middleware
//
#include <Arduino.h>
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
static AsyncWebServer server(80);
static AsyncCorsMiddleware cors;
void setup() {
Serial.begin(115200);
#if ASYNCWEBSERVER_WIFI_SUPPORTED
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
cors.setOrigin("http://192.168.4.1");
cors.setMethods("POST, GET, OPTIONS, DELETE");
cors.setHeaders("X-Custom-Header");
cors.setAllowCredentials(false);
cors.setMaxAge(600);
server.addMiddleware(&cors);
// Test CORS preflight request
// curl -v -X OPTIONS -H "origin: http://192.168.4.1" http://192.168.4.1/cors
//
// Test CORS request
// curl -v -H "origin: http://192.168.4.1" http://192.168.4.1/cors
//
// Test non-CORS request
// curl -v http://192.168.4.1/cors
//
server.on("/cors", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "text/plain", "Hello, world!");
});
server.begin();
}
// not needed
void loop() {
delay(100);
}

View File

@@ -1,60 +1,47 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2026 Hristo Gochkov, Mathieu Carbou, Emil Muratov, Will Miles
#include <DNSServer.h>
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#ifdef ESP32
#include <WiFi.h>
#include <AsyncTCP.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include "ESPAsyncWebServer.h"
static DNSServer dnsServer;
static AsyncWebServer server(80);
DNSServer dnsServer;
AsyncWebServer server(80);
class CaptiveRequestHandler : public AsyncWebHandler {
public:
bool canHandle(__unused AsyncWebServerRequest *request) const override {
CaptiveRequestHandler() {}
virtual ~CaptiveRequestHandler() {}
bool canHandle(AsyncWebServerRequest *request){
//request->addInterestingHeader("ANY");
return true;
}
void handleRequest(AsyncWebServerRequest *request) {
AsyncResponseStream *response = request->beginResponseStream("text/html");
response->print("<!DOCTYPE html><html><head><title>Captive Portal</title></head><body>");
response->print("<p>This is our captive portal front page.</p>");
response->print("<p>This is out captive portal front page.</p>");
response->printf("<p>You were trying to reach: http://%s%s</p>", request->host().c_str(), request->url().c_str());
#if ASYNCWEBSERVER_WIFI_SUPPORTED
response->printf("<p>Try opening <a href='http://%s'>this link</a> instead</p>", WiFi.softAPIP().toString().c_str());
#endif
response->print("</body></html>");
request->send(response);
}
};
void setup() {
Serial.begin(115200);
Serial.println();
Serial.println("Configuring access point...");
#if ASYNCWEBSERVER_WIFI_SUPPORTED
if (!WiFi.softAP("esp-captive")) {
Serial.println("Soft AP creation failed.");
while (1);
}
void setup(){
//your other setup stuff...
WiFi.softAP("esp-captive");
dnsServer.start(53, "*", WiFi.softAPIP());
#endif
server.addHandler(new CaptiveRequestHandler()).setFilter(ON_AP_FILTER); // only when requested from AP
// more handlers...
server.addHandler(new CaptiveRequestHandler()).setFilter(ON_AP_FILTER);//only when requested from AP
//more handlers...
server.begin();
}
void loop() {
void loop(){
dnsServer.processNextRequest();
}

View File

@@ -1,133 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2026 Hristo Gochkov, Mathieu Carbou, Emil Muratov, Will Miles
//
// Shows how to catch all requests and send a 404 Not Found response
//
#include <Arduino.h>
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
static AsyncWebServer server(80);
static const char *htmlContent PROGMEM = R"(
<!DOCTYPE html>
<html>
<head>
<title>Sample HTML</title>
</head>
<body>
<h1>Hello, World!</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
</body>
</html>
)";
static const size_t htmlContentLength = strlen_P(htmlContent);
void setup() {
Serial.begin(115200);
#if ASYNCWEBSERVER_WIFI_SUPPORTED
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);
});
// catch any request, and send a 404 Not Found response
// except for /game_log which is handled by onRequestBody
//
// curl -v http://192.168.4.1/foo
//
server.onNotFound([](AsyncWebServerRequest *request) {
if (request->url() == "/game_log") {
return; // response object already created by onRequestBody
}
request->send(404, "text/plain", "Not found");
});
// See: https://github.com/ESP32Async/ESPAsyncWebServer/issues/6
// catch any POST request and send a 200 OK response
//
// curl -v -X POST http://192.168.4.1/game_log -H "Content-Type: application/json" -d '{"game": "test"}'
//
server.onRequestBody([](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
if (request->url() == "/game_log") {
request->send(200, "application/json", "{\"status\":\"OK\"}");
}
// note that there is no else here: the goal is only to prepare a response based on some body content
// onNotFound will always be called after this, and will not override the response object if `/game_log` is requested
});
server.begin();
}
// not needed
void loop() {
delay(100);
}

View File

@@ -1,225 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2026 Hristo Gochkov, Mathieu Carbou, Emil Muratov, Mitch Bradley
//
// - Test for chunked encoding in requests
//
#include <Arduino.h>
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
#include <LittleFS.h>
using namespace asyncsrv;
// Tests:
//
// Upload a file with PUT
// curl -T myfile.txt http://192.168.4.1/
//
// Upload a file with PUT using chunked encoding
// curl -T bigfile.txt -H 'Transfer-Encoding: chunked' http://192.168.4.1/
// ** Note: If the file will not fit in the available space, the server
// ** does not know that in advance due to the lack of a Content-Length header.
// ** The transfer will proceed until the filesystem fills up, then the transfer
// ** will fail and the partial file will be deleted. This works correctly with
// ** recent versions (e.g. pioarduino) of the arduinoespressif32 framework, but
// ** fails with the stale 3.20017.241212+sha.dcc1105b version due to a LittleFS
// ** bug that has since been fixed.
//
// Immediately reject a chunked PUT that will not fit in available space
// curl -T bigfile.txt -H 'Transfer-Encoding: chunked' -H 'X-Expected-Entity-Length: 99999999' http://192.168.4.1/
// ** Note: MacOS WebDAVFS supplies the X-Expected-Entity-Length header with its
// ** chunked PUTs
// Malformed chunk (triggers abort)
// printf 'PUT /bad HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n5\r\n12345\r\nZ\r\n' | nc 192.168.4.1 80
// This struct is used with _tempObject to communicate between handleBody and a subsequent handleRequest
struct RequestState {
File outFile;
};
void handleRequest(AsyncWebServerRequest *request) {
Serial.print(request->methodToString());
Serial.print(" ");
Serial.println(request->url());
if (request->method() != HTTP_PUT) {
request->send(400); // Bad Request
return;
}
// If request->_tempObject is not null, handleBody already
// did the necessary work for a PUT operation. Otherwise,
// handleBody was either not called, or did nothing, so we
// handle the request later in this routine. That happens
// when a non-chunked PUT has Content-Length: 0.
auto state = static_cast<RequestState *>(request->_tempObject);
if (state) {
// If handleBody successfully opened the file, whether or not it
// wrote data to it, we close it here and send the "created"
// response. If handleBody did not open the file, because the
// open attempt failed or because the operation was rejected,
// state will be non-null but state->outFile will be false. In
// that case, handleBody has already sent an appropriate
// response code.
if (state->outFile) {
// The file was already opened and written in handleBody so
// we close it here and issue the appropriate response.
state->outFile.close();
request->send(201); // Created
}
// The resources used by state will be automatically freed
// when the framework frees the _tempObject pointer
return;
}
String path = request->url();
// This PUT code executes if the body was empty, which
// can happen if the client creates a zero-length file.
// MacOS WebDAVFS does that, then later LOCKs the file
// and issues a subsequent PUT with body contents.
#ifdef ESP32
File file = LittleFS.open(path, "w", true);
#else
File file = LittleFS.open(path, "w");
#endif
if (file) {
file.close();
request->send(201); // Created
return;
}
request->send(403);
}
void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
if (request->method() == HTTP_PUT) {
auto state = static_cast<RequestState *>(request->_tempObject);
if (index == 0) {
// parse the url to a proper path
String path = request->url();
// Allocate the _tempObject memory
request->_tempObject = std::malloc(sizeof(RequestState));
// Use placement new to construct the RequestState object therein
state = new (request->_tempObject) RequestState{File()};
// If the client disconnects or there is a parsing error,
// handleRequest will not be called so we need to close
// the file. The memory backing _tempObject will be freed
// automatically.
request->onDisconnect([request]() {
Serial.println("Client disconnected");
auto state = static_cast<RequestState *>(request->_tempObject);
if (state) {
if (state->outFile) {
state->outFile.close();
}
}
});
if (total) {
#ifdef ESP32
size_t avail = LittleFS.totalBytes() - LittleFS.usedBytes();
#else
FSInfo info;
LittleFS.info(info);
auto avail = info.totalBytes - info.usedBytes;
#endif
avail = (avail >= 4096) ? avail - 4096 : avail; // Reserve a block for overhead
if (total > avail) {
Serial.printf("PUT %zu bytes will not fit in available space (%zu).\n", total, avail);
request->send(507, "text/plain", "Too large for available storage\r\n");
return;
}
}
Serial.print("Opening ");
Serial.print(path);
Serial.println(" from handleBody");
#ifdef ESP32
File file = LittleFS.open(path, "w", true);
#else
File file = LittleFS.open(path, "w");
#endif
if (!file) {
request->send(500, "text/plain", "Cannot create the file");
return;
}
if (file.isDirectory()) {
file.close();
Serial.println("Cannot PUT to a directory");
request->send(403, "text/plain", "Cannot PUT to a directory");
return;
}
// If we already returned, the File object in
// request->_tempObject is the default-constructed one. The
// presence of a non-default-constructed File in state->outFile
// indicates that the file was opened successfully and is ready
// to receive body data. The File will be closed later when
// handleRequest is called after all calls to handleBody
std::swap(state->outFile, file);
// Now request->_tempObject contains the actual file object which owns it,
// and default-constructed File() object is in file, which will
// go out of scope
}
if (state && state->outFile) {
Serial.printf("Writing %zu bytes at offset %zu\n", len, index);
auto actual = state->outFile.write(data, len);
if (actual != len) {
Serial.println("WebDAV write failed. Deleting file.");
// Replace the File object in state with a null one
File file{};
std::swap(state->outFile, file);
file.close();
String path = request->url();
LittleFS.remove(path);
request->send(507, "text/plain", "Too large for available storage\r\n");
return;
}
}
}
}
static AsyncWebServer server(80);
void setup() {
Serial.begin(115200);
#if ASYNCWEBSERVER_WIFI_SUPPORTED
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
#ifdef ESP32
LittleFS.begin(true);
#else
LittleFS.begin();
#endif
server.onRequestBody(handleBody);
server.onNotFound(handleRequest);
server.begin();
}
void loop() {
delay(100);
}

View File

@@ -1,140 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2026 Hristo Gochkov, Mathieu Carbou, Emil Muratov, Will Miles
//
// Chunk response with caching example
//
#include <Arduino.h>
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
static AsyncWebServer server(80);
static const char *htmlContent PROGMEM = R"(
<!DOCTYPE html>
<html>
<head>
<title>Sample HTML</title>
</head>
<body>
<h1>Hello, World!</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
</body>
</html>
)";
static const size_t htmlContentLength = strlen_P(htmlContent);
void setup() {
Serial.begin(115200);
#if ASYNCWEBSERVER_WIFI_SUPPORTED
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
// first time: serves the file and cache headers
// curl -N -v http://192.168.4.1/ --output -
//
// second time: serves 304
// curl -N -v -H "if-none-match: 4272" http://192.168.4.1/ --output -
//
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
String etag = "\"" + String(htmlContentLength) + "\""; // RFC9110: ETag must be enclosed in double quotes
if (request->header(asyncsrv::T_INM) == etag) {
request->send(304);
return;
}
AsyncWebServerResponse *response = request->beginChunkedResponse("text/html", [](uint8_t *buffer, size_t maxLen, size_t index) -> size_t {
Serial.printf("%u / %u\n", index, htmlContentLength);
// finished ?
if (htmlContentLength <= index) {
Serial.println("finished");
return 0;
}
// serve a maximum of 256 or maxLen bytes of the remaining content
// this small number is specifically chosen to demonstrate the chunking
// DO NOT USE SUCH SMALL NUMBER IN PRODUCTION
// Reducing the chunk size will increase the response time, thus reducing the server's capacity in processing concurrent requests
const int chunkSize = min((size_t)256, min(maxLen, htmlContentLength - index));
Serial.printf("sending: %u\n", chunkSize);
memcpy(buffer, htmlContent + index, chunkSize);
return chunkSize;
});
response->addHeader(asyncsrv::T_Cache_Control, "public,max-age=60");
response->addHeader(asyncsrv::T_ETag, etag);
request->send(response);
});
server.begin();
}
void loop() {
delay(100);
}

View File

@@ -1,223 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2026 Hristo Gochkov, Mathieu Carbou, Emil Muratov, Will Miles
//
// Shows how to wait in a chunk response for incoming data
//
#include <Arduino.h>
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
static const char *htmlContent PROGMEM = R"(
<!DOCTYPE html>
<html>
<head>
<title>Sample HTML</title>
</head>
<body>
<h1>Hello, World!</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
</body>
</html>
)";
static const size_t htmlContentLength = strlen_P(htmlContent);
static AsyncWebServer server(80);
static AsyncLoggingMiddleware requestLogger;
static String triggerUART;
static int key = -1;
void setup() {
Serial.begin(115200);
#if ASYNCWEBSERVER_WIFI_SUPPORTED
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
// adds some internal request logging for debugging
requestLogger.setEnabled(true);
requestLogger.setOutput(Serial);
server.addMiddleware(&requestLogger);
#if ASYNC_JSON_SUPPORT == 1
//
// HOW TO RUN THIS EXAMPLE:
//
// 1. Trigger a request that will be blocked for a long time:
// > time curl -v -X POST http://192.168.4.1/api -H "Content-Type: application/json" -d '{"input": "Please type a key to continue in Serial console..."}' --output -
//
// 2. While waiting, in another terminal, run some concurrent requests:
// > time curl -v http://192.168.4.1/
//
// 3. Type a key in the Serial console to continue the processing within 30 seconds.
// This should unblock the first request.
//
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);
});
server.on(
"/api", HTTP_POST,
[](AsyncWebServerRequest *request) {
// request parsing has finished
String *data = (String *)request->_tempObject;
if (!data) {
request->send(400);
return;
}
// no data ?
if (!data->length()) {
delete data;
request->_tempObject = nullptr;
request->send(400);
return;
}
JsonDocument doc;
// deserialize and check for errors
if (deserializeJson(doc, *data)) {
delete data;
request->_tempObject = nullptr;
request->send(400);
return;
}
delete data;
request->_tempObject = nullptr;
// start UART com: UART will send the data to the Serial console and wait for the key press
triggerUART = doc["input"].as<const char *>();
key = -1;
AsyncWebServerResponse *response = request->beginChunkedResponse("text/plain", [](uint8_t *buffer, size_t maxLen, size_t index) -> size_t {
// still waiting for UARY ?
if (triggerUART.length() && key == -1) {
return RESPONSE_TRY_AGAIN;
}
// finished ?
if (!triggerUART.length() && key == -1) {
return 0; // 0 means we are done
}
// async_ws_log_d("UART answered!");
String answer = "You typed: ";
answer.concat((char)key);
// note: I did not check for maxLen, but you should (see ChunkResponse.ino)
memcpy(buffer, answer.c_str(), answer.length());
// finish!
triggerUART = emptyString;
key = -1;
return answer.length();
});
request->send(response);
},
NULL, // upload handler is not used so it should be NULL
[](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
// async_ws_log_d("Body: index: %u, len: %u, total: %u", index, len, total);
if (!index) {
// async_ws_log_d("Start body parsing");
request->_tempObject = new String();
// cast request->_tempObject pointer to String and reserve total size
((String *)request->_tempObject)->reserve(total);
// set timeout 30s
request->client()->setRxTimeout(30);
}
// async_ws_log_d("Append body data");
((String *)request->_tempObject)->concat((const char *)data, len);
}
);
#endif
server.begin();
}
void loop() {
if (triggerUART.length() && key == -1) {
Serial.println(triggerUART);
// async_ws_log_d("Waiting for UART input...");
while (!Serial.available()) {
delay(100);
}
key = Serial.read();
Serial.flush();
// async_ws_log_d("UART input: %c", key);
triggerUART = emptyString;
}
}

View File

@@ -1,53 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2026 Hristo Gochkov, Mathieu Carbou, Emil Muratov, Will Miles
//
// https://github.com/ESP32Async/ESPAsyncWebServer/discussions/23
//
#include <Arduino.h>
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
static AsyncWebServer server(80);
void setup() {
Serial.begin(115200);
#if ASYNCWEBSERVER_WIFI_SUPPORTED
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "text/plain", "Hello, world");
});
server.begin();
Serial.println("begin() - run: curl -v http://192.168.4.1/ => should succeed");
delay(10000);
Serial.println("end()");
server.end();
Serial.println("waiting before restarting server...");
delay(100);
server.begin();
Serial.println("begin() - run: curl -v http://192.168.4.1/ => should succeed");
}
// not needed
void loop() {
delay(100);
}

View File

@@ -1,136 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2026 Hristo Gochkov, Mathieu Carbou, Emil Muratov, Will Miles
//
// Shows how to use setFilter to route requests to different handlers based on WiFi mode
//
#include <DNSServer.h>
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include "ESPAsyncWebServer.h"
static DNSServer dnsServer;
static AsyncWebServer server(80);
class CaptiveRequestHandler : public AsyncWebHandler {
public:
bool canHandle(__unused AsyncWebServerRequest *request) const override {
return true;
}
void handleRequest(AsyncWebServerRequest *request) override {
AsyncResponseStream *response = request->beginResponseStream("text/html");
response->print("<!DOCTYPE html><html><head><title>Captive Portal</title></head><body>");
response->print("<p>This is out captive portal front page.</p>");
response->printf("<p>You were trying to reach: http://%s%s</p>", request->host().c_str(), request->url().c_str());
#if ASYNCWEBSERVER_WIFI_SUPPORTED
response->printf("<p>Try opening <a href='http://%s'>this link</a> instead</p>", WiFi.softAPIP().toString().c_str());
#endif
response->print("</body></html>");
request->send(response);
}
};
bool hit1 = false;
bool hit2 = false;
void setup() {
Serial.begin(115200);
server
.on(
"/", HTTP_GET,
[](AsyncWebServerRequest *request) {
Serial.println("Captive portal request...");
#if ASYNCWEBSERVER_WIFI_SUPPORTED
Serial.println("WiFi.localIP(): " + WiFi.localIP().toString());
#endif
Serial.println("request->client()->localIP(): " + request->client()->localIP().toString());
#if ESP_IDF_VERSION_MAJOR >= 5
#if ASYNCWEBSERVER_WIFI_SUPPORTED
Serial.println("WiFi.type(): " + String((int)WiFi.localIP().type()));
#endif
Serial.println("request->client()->type(): " + String((int)request->client()->localIP().type()));
#endif
#if ASYNCWEBSERVER_WIFI_SUPPORTED
Serial.println(WiFi.localIP() == request->client()->localIP() ? "should be: ON_STA_FILTER" : "should be: ON_AP_FILTER");
Serial.println(WiFi.localIP() == request->client()->localIP());
Serial.println(WiFi.localIP().toString() == request->client()->localIP().toString());
#endif
request->send(200, "text/plain", "This is the captive portal");
hit1 = true;
}
)
.setFilter(ON_AP_FILTER);
server
.on(
"/", HTTP_GET,
[](AsyncWebServerRequest *request) {
Serial.println("Website request...");
#if ASYNCWEBSERVER_WIFI_SUPPORTED
Serial.println("WiFi.localIP(): " + WiFi.localIP().toString());
#endif
Serial.println("request->client()->localIP(): " + request->client()->localIP().toString());
#if ESP_IDF_VERSION_MAJOR >= 5
#if ASYNCWEBSERVER_WIFI_SUPPORTED
Serial.println("WiFi.type(): " + String((int)WiFi.localIP().type()));
#endif
Serial.println("request->client()->type(): " + String((int)request->client()->localIP().type()));
#endif
#if ASYNCWEBSERVER_WIFI_SUPPORTED
Serial.println(WiFi.localIP() == request->client()->localIP() ? "should be: ON_STA_FILTER" : "should be: ON_AP_FILTER");
Serial.println(WiFi.localIP() == request->client()->localIP());
Serial.println(WiFi.localIP().toString() == request->client()->localIP().toString());
#endif
request->send(200, "text/plain", "This is the website");
hit2 = true;
}
)
.setFilter(ON_STA_FILTER);
// assert(WiFi.softAP("esp-captive-portal"));
// dnsServer.start(53, "*", WiFi.softAPIP());
// server.begin();
// Serial.println("Captive portal started!");
// while (!hit1) {
// dnsServer.processNextRequest();
// yield();
// }
// delay(1000); // Wait for the client to process the response
// Serial.println("Captive portal opened, stopping it and connecting to WiFi...");
// dnsServer.stop();
// WiFi.softAPdisconnect();
#if ASYNCWEBSERVER_WIFI_SUPPORTED
WiFi.persistent(false);
WiFi.begin("IoT");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
}
Serial.println("Connected to WiFi with IP address: " + WiFi.localIP().toString());
#endif
server.begin();
// while (!hit2) {
// delay(10);
// }
// delay(1000); // Wait for the client to process the response
// ESP.restart();
}
void loop() {
delay(100);
}

View File

@@ -1,107 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2026 Hristo Gochkov, Mathieu Carbou, Emil Muratov, Will Miles
//
// Shows how to serve a large HTML page from flash memory without copying it to heap in a temporary buffer
//
#include <Arduino.h>
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
static AsyncWebServer server(80);
static const char *htmlContent PROGMEM = R"(
<!DOCTYPE html>
<html>
<head>
<title>Sample HTML</title>
</head>
<body>
<h1>Hello, World!</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
</body>
</html>
)";
static const size_t htmlContentLength = strlen_P(htmlContent);
void setup() {
Serial.begin(115200);
#if ASYNCWEBSERVER_WIFI_SUPPORTED
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);
});
server.begin();
}
// not needed
void loop() {
delay(100);
}

View File

@@ -1,111 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2026 Hristo Gochkov, Mathieu Carbou, Emil Muratov, Will Miles
//
// Show how to manipulate headers in the request / response
//
#include <Arduino.h>
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
static AsyncWebServer server(80);
// request logger
static AsyncLoggingMiddleware requestLogger;
// filter out specific headers from the incoming request
static AsyncHeaderFilterMiddleware headerFilter;
// remove all headers from the incoming request except the ones provided in the constructor
AsyncHeaderFreeMiddleware headerFree;
void setup() {
Serial.begin(115200);
#if ASYNCWEBSERVER_WIFI_SUPPORTED
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
requestLogger.setEnabled(true);
requestLogger.setOutput(Serial);
headerFilter.filter("X-Remove-Me");
headerFree.keep("X-Keep-Me");
headerFree.keep("host");
server.addMiddlewares({&requestLogger, &headerFilter});
// x-remove-me header will be removed
//
// curl -v -H "X-Header: Foo" -H "x-remove-me: value" http://192.168.4.1/remove
//
server.on("/remove", HTTP_GET, [](AsyncWebServerRequest *request) {
// print all headers
for (size_t i = 0; i < request->headers(); i++) {
const AsyncWebHeader *h = request->getHeader(i);
Serial.printf("Header[%s]: %s\n", h->name().c_str(), h->value().c_str());
}
request->send(200, "text/plain", "Hello, world!");
});
// Only headers x-keep-me and host will be kept
//
// curl -v -H "x-keep-me: value" -H "x-remove-me: value" http://192.168.4.1/keep
//
server
.on(
"/keep", HTTP_GET,
[](AsyncWebServerRequest *request) {
// print all headers
for (size_t i = 0; i < request->headers(); i++) {
const AsyncWebHeader *h = request->getHeader(i);
Serial.printf("Header[%s]: %s\n", h->name().c_str(), h->value().c_str());
}
request->send(200, "text/plain", "Hello, world!");
}
)
.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();
}
// not needed
void loop() {
delay(100);
}

View File

@@ -1,69 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2026 Hristo Gochkov, Mathieu Carbou, Emil Muratov, Will Miles
//
// Query and send headers
//
#include <Arduino.h>
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
static AsyncWebServer server(80);
void setup() {
Serial.begin(115200);
#if ASYNCWEBSERVER_WIFI_SUPPORTED
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
//
// curl -v http://192.168.4.1
//
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
//List all collected headers
int headers = request->headers();
int i;
for (i = 0; i < headers; i++) {
const AsyncWebHeader *h = request->getHeader(i);
Serial.printf("HEADER[%s]: %s\n", h->name().c_str(), h->value().c_str());
}
AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", "Hello World!");
//Add header to the response
response->addHeader("Server", "ESP Async Web Server");
//Add multiple headers with the same name
response->addHeader("Set-Cookie", "sessionId=38afes7a8", false);
response->addHeader("Set-Cookie", "id=a3fWa; Max-Age=2592000", false);
response->addHeader("Set-Cookie", "qwerty=219ffwef9w0f; Domain=example.com", false);
//Remove specific header
response->removeHeader("Set-Cookie", "sessionId=38afes7a8");
//Remove all headers with the same name
response->removeHeader("Set-Cookie");
request->send(response);
});
server.begin();
}
void loop() {
//Sleep in the loop task to not keep the CPU busy
delay(1000);
}

View File

@@ -1,123 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2026 Hristo Gochkov, Mathieu Carbou, Emil Muratov, Will Miles
//
// Shows how to send and receive Json data
//
#include <Arduino.h>
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
static AsyncWebServer server(80);
#if ASYNC_JSON_SUPPORT == 1
static AsyncCallbackJsonWebHandler *handler = new AsyncCallbackJsonWebHandler("/json2");
#endif
void setup() {
Serial.begin(115200);
#if ASYNCWEBSERVER_WIFI_SUPPORTED
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
#if ASYNC_JSON_SUPPORT == 1
//
// sends JSON using AsyncJsonResponse
//
// curl -v http://192.168.4.1/json1
//
server.on("/json1", HTTP_GET, [](AsyncWebServerRequest *request) {
AsyncJsonResponse *response = new AsyncJsonResponse();
JsonObject root = response->getRoot().to<JsonObject>();
root["hello"] = "world";
response->setLength();
request->send(response);
});
// Send JSON using AsyncResponseStream
//
// curl -v http://192.168.4.1/json2
//
server.on("/json2", HTTP_GET, [](AsyncWebServerRequest *request) {
AsyncResponseStream *response = request->beginResponseStream("application/json");
JsonDocument doc;
JsonObject root = doc.to<JsonObject>();
root["foo"] = "bar";
// serializeJson(root, Serial);
// Serial.println();
request->send(response);
});
// curl -v -X POST -H 'Content-Type: application/json' -d '{"name":"You"}' http://192.168.4.1/json2
// curl -v -X PUT -H 'Content-Type: application/json' -d '{"name":"You"}' http://192.168.4.1/json2
//
// edge cases:
//
// curl -v -X POST -H "Content-Type: application/json" -d "1234" -H "Content-Length: 5" http://192.168.4.1/json2 => rx timeout
// curl -v -X POST -H "Content-Type: application/json" -d "1234" -H "Content-Length: 2" http://192.168.4.1/json2 => 12
// curl -v -X POST -H "Content-Type: application/json" -d "1234" -H "Content-Length: 4" http://192.168.4.1/json2 => 1234
// curl -v -X POST -H "Content-Type: application/json" -d "1234" -H "Content-Length: 10" http://192.168.4.1/json2 => rx timeout
// curl -v -X POST -H "Content-Type: application/json" -d "12345678" -H "Content-Length: 8" http://192.168.4.1/json2 => 12345678
// curl -v -X POST -H "Content-Type: application/json" -d "123456789" -H "Content-Length: 8" http://192.168.4.1/json2 => 12345678
// curl -v -X POST -H "Content-Type: application/json" -d "123456789" -H "Content-Length: 9" http://192.168.4.1/json2 => 413: Content length exceeds maximum allowed
handler->setMaxContentLength(8);
handler->setMethod(HTTP_POST | HTTP_PUT);
handler->onRequest([](AsyncWebServerRequest *request, JsonVariant &json) {
serializeJson(json, Serial);
Serial.println();
AsyncJsonResponse *response = new AsyncJsonResponse();
JsonObject root = response->getRoot().to<JsonObject>();
root["hello"] = json.as<JsonObject>()["name"];
response->setLength();
request->send(response);
});
server.addHandler(handler);
// New Json API since 3.8.2, which works for both Json and MessagePack bodies
// curl -v -X POST -H 'Content-Type: application/json' -d '{"name":"You"}' http://192.168.4.1/json3
server.on("/json3", HTTP_POST, [](AsyncWebServerRequest *request, JsonVariant &json) {
Serial.printf("Body request : ");
serializeJson(json, Serial);
Serial.println();
AsyncJsonResponse *response = new AsyncJsonResponse();
JsonObject root = response->getRoot().to<JsonObject>();
root["hello"] = json.as<JsonObject>()["name"];
response->setLength();
request->send(response);
});
#endif
server.begin();
}
static uint32_t lastHeapTime = 0;
static uint32_t lastHeap = 0;
void loop() {
#ifdef ESP32
uint32_t now = millis();
if (now - lastHeapTime >= 500) {
uint32_t heap = ESP.getFreeHeap();
if (heap != lastHeap) {
lastHeap = heap;
async_ws_log_w("Free heap: %" PRIu32, heap);
}
lastHeapTime = now;
}
#endif
}

View File

@@ -1,178 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2026 Hristo Gochkov, Mathieu Carbou, Emil Muratov, Will Miles
//
// Example to send a large response and control the filling of the buffer.
//
// This is also a MRE for:
// - https://github.com/ESP32Async/ESPAsyncWebServer/issues/242
// - https://github.com/ESP32Async/ESPAsyncWebServer/issues/315
//
#include <Arduino.h>
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
static AsyncWebServer server(80);
static const size_t totalResponseSize = 16 * 1000; // 16 KB
static char fillChar = 'A';
class CustomResponse : public AsyncAbstractResponse {
public:
explicit CustomResponse() {
_code = 200;
_contentType = "text/plain";
_sendContentLength = false;
}
bool _sourceValid() const override {
return true;
}
size_t _fillBuffer(uint8_t *buf, size_t buflen) override {
if (_sent == RESPONSE_TRY_AGAIN) {
Serial.println("Simulating temporary unavailability of data...");
_sent = 0;
return RESPONSE_TRY_AGAIN;
}
size_t remaining = totalResponseSize - _sent;
if (remaining == 0) {
return 0;
}
if (buflen > remaining) {
buflen = remaining;
}
Serial.printf("Filling '%c' @ sent: %u, buflen: %u\n", fillChar, _sent, buflen);
std::fill_n(buf, buflen, static_cast<uint8_t>(fillChar));
_sent += buflen;
fillChar = (fillChar == 'Z') ? 'A' : fillChar + 1;
return buflen;
}
private:
char fillChar = 'A';
size_t _sent = 0;
};
// Code to reproduce issues:
// - https://github.com/ESP32Async/ESPAsyncWebServer/issues/242
// - https://github.com/ESP32Async/ESPAsyncWebServer/issues/315
//
// https://github.com/ESP32Async/ESPAsyncWebServer/pull/317#issuecomment-3421141039
//
// I cracked it.
// So this is how it works:
// That space that _tcp is writing to identified by CONFIG_TCP_SND_BUF_DEFAULT (and is value-matching with default TCP windows size which is very confusing itself).
// The space returned by client()->write() and client->space() somehow might not be atomically/thread synced (had not dived that deep yet). So if first call to _fillBuffer is done via user-code thread and ended up with some small amount of data consumed and second one is done by _poll or _ack? returns full size again! This is where old code fails.
// If you change your class this way it will fail 100%.
class CustomResponseMRE : public AsyncAbstractResponse {
public:
explicit CustomResponseMRE() {
_code = 200;
_contentType = "text/plain";
_sendContentLength = false;
// add some useless headers
addHeader("Clear-Site-Data", "Clears browsing data (e.g., cookies, storage, cache) associated with the requesting website.");
addHeader(
"No-Vary-Search", "Specifies a set of rules that define how a URL's query parameters will affect cache matching. These rules dictate whether the same "
"URL with different URL parameters should be saved as separate browser cache entries"
);
}
bool _sourceValid() const override {
return true;
}
size_t _fillBuffer(uint8_t *buf, size_t buflen) override {
if (fillChar == NULL) {
fillChar = 'A';
return RESPONSE_TRY_AGAIN;
}
if (_sent == RESPONSE_TRY_AGAIN) {
Serial.println("Simulating temporary unavailability of data...");
_sent = 0;
return RESPONSE_TRY_AGAIN;
}
size_t remaining = totalResponseSize - _sent;
if (remaining == 0) {
return 0;
}
if (buflen > remaining) {
buflen = remaining;
}
Serial.printf("Filling '%c' @ sent: %u, buflen: %u\n", fillChar, _sent, buflen);
std::fill_n(buf, buflen, static_cast<uint8_t>(fillChar));
_sent += buflen;
fillChar = (fillChar == 'Z') ? 'A' : fillChar + 1;
return buflen;
}
private:
char fillChar = NULL;
size_t _sent = 0;
};
void setup() {
Serial.begin(115200);
#if ASYNCWEBSERVER_WIFI_SUPPORTED
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
// Example to use a AwsResponseFiller
//
// curl -v http://192.168.4.1/1 | grep -o '.' | sort | uniq -c
//
// Should output 16000 and a distribution of letters which is the same in ESP32 logs and console
//
server.on("/1", HTTP_GET, [](AsyncWebServerRequest *request) {
fillChar = 'A';
AsyncWebServerResponse *response = request->beginResponse("text/plain", totalResponseSize, [](uint8_t *buffer, size_t maxLen, size_t index) -> size_t {
size_t remaining = totalResponseSize - index;
size_t toSend = (remaining < maxLen) ? remaining : maxLen;
Serial.printf("Filling '%c' @ index: %u, maxLen: %u, toSend: %u\n", fillChar, index, maxLen, toSend);
std::fill_n(buffer, toSend, static_cast<uint8_t>(fillChar));
fillChar = (fillChar == 'Z') ? 'A' : fillChar + 1;
return toSend;
});
request->send(response);
});
// Example to use a AsyncAbstractResponse
//
// curl -v http://192.168.4.1/2 | grep -o '.' | sort | uniq -c
//
// Should output 16000 and a distribution of letters which is the same in ESP32 logs and console
//
server.on("/2", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(new CustomResponse());
});
// Example to use a AsyncAbstractResponse
//
// curl -v http://192.168.4.1/3 | grep -o '.' | sort | uniq -c
//
// Should output 16000 and a distribution of letters which is the same in ESP32 logs and console
//
server.on("/3", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(new CustomResponseMRE());
});
server.begin();
}
void loop() {
delay(100);
}

View File

@@ -1,49 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2026 Hristo Gochkov, Mathieu Carbou, Emil Muratov, Will Miles
//
// Show how to log the incoming request and response as a curl-like syntax
//
#include <Arduino.h>
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
static AsyncWebServer server(80);
static AsyncLoggingMiddleware requestLogger;
void setup() {
Serial.begin(115200);
#if ASYNCWEBSERVER_WIFI_SUPPORTED
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
requestLogger.setEnabled(true);
requestLogger.setOutput(Serial);
server.addMiddleware(&requestLogger);
// curl -v -H "X-Header:Foo" http://192.168.4.1/
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "text/plain", "Hello, world!");
});
server.begin();
}
// not needed
void loop() {
delay(100);
}

View File

@@ -1,106 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2026 Hristo Gochkov, Mathieu Carbou, Emil Muratov, Will Miles
//
// Shows how to send and receive Message Pack data
//
#include <Arduino.h>
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
static AsyncWebServer server(80);
#if ASYNC_JSON_SUPPORT == 1
static AsyncCallbackJsonWebHandler *handler = new AsyncCallbackJsonWebHandler("/msgpack2");
#endif
void setup() {
Serial.begin(115200);
#if ASYNCWEBSERVER_WIFI_SUPPORTED
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
#if ASYNC_JSON_SUPPORT == 1
//
// sends MessagePack using AsyncMessagePackResponse
//
// curl -v http://192.168.4.1/msgpack1
//
server.on("/msgpack1", HTTP_GET, [](AsyncWebServerRequest *request) {
AsyncMessagePackResponse *response = new AsyncMessagePackResponse();
JsonObject root = response->getRoot().to<JsonObject>();
root["hello"] = "world";
response->setLength();
request->send(response);
});
// Send MessagePack using AsyncResponseStream
//
// curl -v http://192.168.4.1/msgpack2
//
// Save file: curl -v http://192.168.4.1/msgpack2 -o msgpack.bin
//
server.on("/msgpack2", HTTP_GET, [](AsyncWebServerRequest *request) {
AsyncResponseStream *response = request->beginResponseStream("application/msgpack");
JsonDocument doc;
JsonObject root = doc.to<JsonObject>();
root["name"] = "Bob";
serializeMsgPack(root, *response);
request->send(response);
});
// POST file:
//
// curl -v -X POST -H 'Content-Type: application/msgpack' --data-binary @msgpack.bin http://192.168.4.1/msgpack2
//
handler->setMethod(HTTP_POST | HTTP_PUT);
handler->onRequest([](AsyncWebServerRequest *request, JsonVariant &json) {
Serial.printf("Body request /msgpack2 : "); // should print: Body request /msgpack2 : {"name":"Bob"}
serializeJson(json, Serial);
Serial.println();
AsyncMessagePackResponse *response = new AsyncMessagePackResponse();
JsonObject root = response->getRoot().to<JsonObject>();
root["hello"] = json.as<JsonObject>()["name"];
response->setLength();
request->send(response);
});
server.addHandler(handler);
// New Json API since 3.8.2, which works for both Json and MessagePack bodies
//
// curl -v -X POST -H 'Content-Type: application/json' -d '{"name":"You"}' http://192.168.4.1/msgpack3
// curl -v -X POST -H 'Content-Type: application/msgpack' --data-binary @msgpack.bin http://192.168.4.1/msgpack3
//
server.on("/msgpack3", HTTP_POST, [](AsyncWebServerRequest *request, JsonVariant &json) {
Serial.printf("Body request /msgpack3 : "); // should print: Body request /msgpack3 : {"name":"Bob"}
serializeJson(json, Serial);
Serial.println();
AsyncJsonResponse *response = new AsyncJsonResponse();
JsonObject root = response->getRoot().to<JsonObject>();
root["hello"] = json.as<JsonObject>()["name"];
response->setLength();
request->send(response);
});
#endif
server.begin();
}
// not needed
void loop() {
delay(100);
}

View File

@@ -1,82 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2026 Hristo Gochkov, Mathieu Carbou, Emil Muratov, Will Miles
//
// Show how to sue Middleware
//
#include <Arduino.h>
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
static AsyncWebServer server(80);
// New middleware classes can be created!
class MyMiddleware : public AsyncMiddleware {
public:
void run(AsyncWebServerRequest *request, ArMiddlewareNext next) override {
Serial.printf("Before handler: %s %s\n", request->methodToString(), request->url().c_str());
next(); // continue middleware chain
Serial.printf("After handler: response code=%d\n", request->getResponse()->code());
}
};
void setup() {
Serial.begin(115200);
#if ASYNCWEBSERVER_WIFI_SUPPORTED
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
// add a global middleware to the server
server.addMiddleware(new MyMiddleware());
// Test with:
//
// - curl -v http://192.168.4.1/ => 200 OK
// - curl -v http://192.168.4.1/?user=anon => 403 Forbidden
// - curl -v http://192.168.4.1/?user=foo => 200 OK
// - curl -v http://192.168.4.1/?user=error => 400 ERROR
//
AsyncCallbackWebHandler &handler = server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
Serial.printf("In Handler: %s %s\n", request->methodToString(), request->url().c_str());
request->send(200, "text/plain", "Hello, world!");
});
// add a middleware to this handler only to send 403 if the user is anon
handler.addMiddleware([](AsyncWebServerRequest *request, ArMiddlewareNext next) {
Serial.println("Checking user=anon");
if (request->hasParam("user") && request->getParam("user")->value() == "anon") {
request->send(403, "text/plain", "Forbidden");
} else {
next();
}
});
// add a middleware to this handler that will replace the previously created response by another one
handler.addMiddleware([](AsyncWebServerRequest *request, ArMiddlewareNext next) {
next();
Serial.println("Checking user=error");
if (request->hasParam("user") && request->getParam("user")->value() == "error") {
request->send(400, "text/plain", "ERROR");
}
});
server.begin();
}
// not needed
void loop() {
delay(100);
}

View File

@@ -1,122 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2026 Hristo Gochkov, Mathieu Carbou, Emil Muratov, Will Miles
//
// Query parameters and body parameters
//
#include <Arduino.h>
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
static AsyncWebServer server(80);
static const char *htmlContent PROGMEM = R"(
<!DOCTYPE html>
<html>
<head>
<title>POST Request with Multiple Parameters</title>
</head>
<body>
<form action="http://192.168.4.1" method="POST">
<label for="who">Who?</label>
<input type="text" id="who" name="who" value="Carl"><br>
<label for="g0">g0:</label>
<input type="text" id="g0" name="g0" value="1"><br>
<label for="a0">a0:</label>
<input type="text" id="a0" name="a0" value="2"><br>
<label for="n0">n0:</label>
<input type="text" id="n0" name="n0" value="3"><br>
<label for="t10">t10:</label>
<input type="text" id="t10" name="t10" value="3"><br>
<label for="t20">t20:</label>
<input type="text" id="t20" name="t20" value="4"><br>
<label for="t30">t30:</label>
<input type="text" id="t30" name="t30" value="5"><br>
<label for="t40">t40:</label>
<input type="text" id="t40" name="t40" value="6"><br>
<label for="t50">t50:</label>
<input type="text" id="t50" name="t50" value="7"><br>
<label for="g1">g1:</label>
<input type="text" id="g1" name="g1" value="2"><br>
<label for="a1">a1:</label>
<input type="text" id="a1" name="a1" value="2"><br>
<label for="n1">n1:</label>
<input type="text" id="n1" name="n1" value="3"><br>
<label for="t11">t11:</label>
<input type="text" id="t11" name="t11" value="13"><br>
<label for="t21">t21:</label>
<input type="text" id="t21" name="t21" value="14"><br>
<label for="t31">t31:</label>
<input type="text" id="t31" name="t31" value="15"><br>
<label for="t41">t41:</label>
<input type="text" id="t41" name="t41" value="16"><br>
<label for="t51">t51:</label>
<input type="text" id="t51" name="t51" value="17"><br>
<input type="submit" value="Submit">
</form>
</body>
</html>
)";
static const size_t htmlContentLength = strlen_P(htmlContent);
void setup() {
Serial.begin(115200);
#if ASYNCWEBSERVER_WIFI_SUPPORTED
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
// Get query parameters
//
// curl -v http://192.168.4.1/?who=Bob
//
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
if (request->hasParam("who")) {
Serial.printf("Who? %s\n", request->getParam("who")->value().c_str());
}
request->send(200, "text/html", (uint8_t *)htmlContent, htmlContentLength);
});
// Get form body parameters
//
// curl -v -H "Content-Type: application/x-www-form-urlencoded" -d "who=Carl" -d "param=value" http://192.168.4.1/
//
server.on("/", HTTP_POST, [](AsyncWebServerRequest *request) {
// display params
size_t count = request->params();
for (size_t i = 0; i < count; i++) {
const AsyncWebParameter *p = request->getParam(i);
Serial.printf("PARAM[%u]: %s = %s\n", i, p->name().c_str(), p->value().c_str());
}
// get who param
String who;
if (request->hasParam("who", true)) {
who = request->getParam("who", true)->value();
} else {
who = "No message sent";
}
request->send(200, "text/plain", "Hello " + who + "!");
});
server.begin();
}
// not needed
void loop() {
delay(100);
}

View File

@@ -1,130 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2026 Hristo Gochkov, Mathieu Carbou, Emil Muratov, Will Miles
//
// - Download ESP32 partition by name and/or type and/or subtype
// - Support encrypted and non-encrypted partitions
//
#include <Arduino.h>
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
#include <LittleFS.h>
#ifndef ESP32
// this example is only for the ESP32
void setup() {}
void loop() {}
#else
#include <esp_partition.h>
static AsyncWebServer server(80);
void setup() {
Serial.begin(115200);
#if ASYNCWEBSERVER_WIFI_SUPPORTED
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
LittleFS.begin(true);
// To upload the FS partition, run:
// > pio run -e arduino-3 -t buildfs
// > pio run -e arduino-3 -t uploadfs
//
// Examples:
//
// - Download the partition named "spiffs": http://192.168.4.1/partition?label=spiffs
// - Download the partition named "spiffs" with type "data": http://192.168.4.1/partition?label=spiffs&type=1
// - Download the partition named "spiffs" with type "data" and subtype "spiffs": http://192.168.4.1/partition?label=spiffs&type=1&subtype=130
// - Download the partition with subtype "nvs": http://192.168.4.1/partition?type=1&subtype=2
//
// "type" and "subtype" IDs can be found in esp_partition.h header file.
//
// Add "&raw=false" parameter to download the partition unencrypted (for encrypted partitions).
// By default, the raw partition is downloaded, so if a partition is encrypted, the encrypted data will be downloaded.
//
// To browse a downloaded LittleFS partition, you can use https://tniessen.github.io/littlefs-disk-img-viewer/ (block size is 4096)
//
server.on("/partition", HTTP_GET, [](AsyncWebServerRequest *request) {
const AsyncWebParameter *pLabel = request->getParam("label");
const AsyncWebParameter *pType = request->getParam("type");
const AsyncWebParameter *pSubtype = request->getParam("subtype");
const AsyncWebParameter *pRaw = request->getParam("raw");
if (!pLabel && !pType && !pSubtype) {
request->send(400, "text/plain", "Bad request: missing parameter");
return;
}
esp_partition_type_t type = ESP_PARTITION_TYPE_ANY;
esp_partition_subtype_t subtype = ESP_PARTITION_SUBTYPE_ANY;
const char *label = nullptr;
bool raw = true;
if (pLabel) {
label = pLabel->value().c_str();
}
if (pType) {
type = (esp_partition_type_t)pType->value().toInt();
}
if (pSubtype) {
subtype = (esp_partition_subtype_t)pSubtype->value().toInt();
}
if (pRaw && pRaw->value() == "false") {
raw = false;
}
const esp_partition_t *partition = esp_partition_find_first(type, subtype, label);
if (!partition) {
request->send(404, "text/plain", "Partition not found");
return;
}
AsyncWebServerResponse *response =
request->beginChunkedResponse("application/octet-stream", [partition, raw](uint8_t *buffer, size_t maxLen, size_t index) -> size_t {
const size_t remaining = partition->size - index;
if (!remaining) {
return 0;
}
const size_t len = std::min(maxLen, remaining);
if (raw && esp_partition_read_raw(partition, index, buffer, len) == ESP_OK) {
return len;
}
if (!raw && esp_partition_read(partition, index, buffer, len) == ESP_OK) {
return len;
}
return 0;
});
response->addHeader("Content-Disposition", "attachment; filename=" + String(partition->label) + ".bin");
response->setContentLength(partition->size);
request->send(response);
});
server.begin();
}
void loop() {
delay(100);
}
#endif

View File

@@ -1,246 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2026 Hristo Gochkov, Mathieu Carbou, Emil Muratov, Will Miles
//
// Perf tests
//
#include <Arduino.h>
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
static const char *htmlContent PROGMEM = R"(
<!DOCTYPE html>
<html>
<head>
<title>Sample HTML</title>
</head>
<body>
<h1>Hello, World!</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
</body>
</html>
)";
static const size_t htmlContentLength = strlen_P(htmlContent);
static constexpr char characters[] = "0123456789ABCDEF";
static size_t charactersIndex = 0;
static AsyncWebServer server(80);
static AsyncEventSource events("/events");
static volatile size_t requests = 0;
void setup() {
Serial.begin(115200);
#if ASYNCWEBSERVER_WIFI_SUPPORTED
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
// Pauses in the request parsing phase
//
// autocannon -c 32 -w 32 -a 96 -t 30 --renderStatusCodes -m POST -H "Content-Type: application/json" -b '{"foo": "bar"}' http://192.168.4.1/delay
//
// curl -v -X POST -H "Content-Type: application/json" -d '{"game": "test"}' http://192.168.4.1/delay
//
server.onNotFound([](AsyncWebServerRequest *request) {
requests = requests + 1;
if (request->url() == "/delay") {
request->send(200, "application/json", "{\"status\":\"OK\"}");
} else {
request->send(404, "text/plain", "Not found");
}
});
server.onRequestBody([](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
if (request->url() == "/delay") {
delay(3000);
}
});
// HTTP endpoint
//
// > autocannon -c 16 -w 16 -d 20 --renderStatusCodes http://192.168.4.1/
// > ab -c 16 -t 20 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
requests = requests + 1;
request->send(200, "text/html", (uint8_t *)htmlContent, htmlContentLength);
});
// IMPORTANT - DO NOT WRITE SUCH CODE IN PRODUCTON !
//
// This example simulates the slowdown that can happen when:
// - downloading a huge file from sdcard
// - doing some file listing on SDCard because it is horribly slow to get a file listing with file stats on SDCard.
// So in both cases, ESP would deadlock or TWDT would trigger.
//
// This example simulats that by slowing down the chunk callback:
// - d=2000 is the delay in ms in the callback
// - l=10000 is the length of the response
//
// time curl -N -v -G -d 'd=2000' -d 'l=10000' http://192.168.4.1/slow.html --output -
//
// THIS CODE WILL CRASH BECAUSE OF THE WATCHDOG.
// IF YOU REALLY NEED TO DO THIS, YOU MUST DISABLE THE TWDT
//
// CORRECT WAY IS TO USE SSE OR WEBSOCKETS TO DO THE COSTLY PROCESSING ASYNC.
//
server.on("/slow.html", HTTP_GET, [](AsyncWebServerRequest *request) {
requests = requests + 1;
uint32_t d = request->getParam("d")->value().toInt();
uint32_t l = request->getParam("l")->value().toInt();
Serial.printf("d = %" PRIu32 ", l = %" PRIu32 "\n", d, l);
AsyncWebServerResponse *response = request->beginChunkedResponse("text/html", [d, l](uint8_t *buffer, size_t maxLen, size_t index) -> size_t {
Serial.printf("%u\n", index);
// finished ?
if (index >= l) {
return 0;
}
// slow down the task to simulate some heavy processing, like SD card reading
delay(d);
memset(buffer, characters[charactersIndex], 256);
charactersIndex = (charactersIndex + 1) % sizeof(characters);
return 256;
});
request->send(response);
});
// SSS endpoint
//
// launch 16 concurrent workers for 30 seconds
// > for i in {1..16}; do ( count=$(gtimeout 30 curl -s -N -H "Accept: text/event-stream" http://192.168.4.1/events 2>&1 | grep -c "^data:"); echo "Total: $count events, $(echo "$count / 4" | bc -l) events / second" ) & done;
//
// With AsyncTCP, with 16 workers: a lot of "Event message queue overflow: discard message", no crash
//
// Total: 1711 events, 427.75 events / second
// Total: 1711 events, 427.75 events / second
// Total: 1626 events, 406.50 events / second
// Total: 1562 events, 390.50 events / second
// Total: 1706 events, 426.50 events / second
// Total: 1659 events, 414.75 events / second
// Total: 1624 events, 406.00 events / second
// Total: 1706 events, 426.50 events / second
// Total: 1487 events, 371.75 events / second
// Total: 1573 events, 393.25 events / second
// Total: 1569 events, 392.25 events / second
// Total: 1559 events, 389.75 events / second
// Total: 1560 events, 390.00 events / second
// Total: 1562 events, 390.50 events / second
// Total: 1626 events, 406.50 events / second
//
// With AsyncTCP, with 10 workers:
//
// Total: 2038 events, 509.50 events / second
// Total: 2120 events, 530.00 events / second
// Total: 2119 events, 529.75 events / second
// Total: 2038 events, 509.50 events / second
// Total: 2037 events, 509.25 events / second
// Total: 2119 events, 529.75 events / second
// Total: 2119 events, 529.75 events / second
// Total: 2120 events, 530.00 events / second
// Total: 2038 events, 509.50 events / second
// Total: 2038 events, 509.50 events / second
//
// With AsyncTCPSock, with 16 workers: ESP32 CRASH !!!
//
// With AsyncTCPSock, with 10 workers:
//
// Total: 1242 events, 310.50 events / second
// Total: 1242 events, 310.50 events / second
// Total: 1242 events, 310.50 events / second
// Total: 1242 events, 310.50 events / second
// Total: 1181 events, 295.25 events / second
// Total: 1182 events, 295.50 events / second
// Total: 1240 events, 310.00 events / second
// Total: 1181 events, 295.25 events / second
// Total: 1181 events, 295.25 events / second
// Total: 1183 events, 295.75 events / second
//
server.addHandler(&events);
server.begin();
}
static uint32_t lastSSE = 0;
static uint32_t deltaSSE = 10;
static uint32_t lastHeap = 0;
void loop() {
uint32_t now = millis();
if (now - lastSSE >= deltaSSE) {
events.send(String("ping-") + now, "heartbeat", now);
lastSSE = millis();
}
#ifdef ESP32
if (now - lastHeap >= 2000) {
Serial.printf("Uptime: %3lu s, requests: %3u, Free heap: %" PRIu32 "\n", millis() / 1000, requests, ESP.getFreeHeap());
lastHeap = now;
}
#endif
}

View File

@@ -1,64 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2026 Hristo Gochkov, Mathieu Carbou, Emil Muratov, Will Miles
//
// Show how to rate limit the server or some endpoints
//
#include <Arduino.h>
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
static AsyncWebServer server(80);
static AsyncRateLimitMiddleware rateLimit;
void setup() {
Serial.begin(115200);
#if ASYNCWEBSERVER_WIFI_SUPPORTED
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
// maximum 5 requests per 10 seconds
rateLimit.setMaxRequests(5);
rateLimit.setWindowSize(10);
// run quickly several times:
//
// curl -v http://192.168.4.1/
//
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "text/plain", "Hello, world!");
});
// run quickly several times:
//
// curl -v http://192.168.4.1/rate-limited
//
server
.on(
"/rate-limited", HTTP_GET,
[](AsyncWebServerRequest *request) {
request->send(200, "text/plain", "Hello, world!");
}
)
.addMiddleware(&rateLimit); // only rate limit this endpoint, but could be applied globally to the server
server.begin();
}
// not needed
void loop() {
delay(100);
}

View File

@@ -1,48 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2026 Hristo Gochkov, Mathieu Carbou, Emil Muratov, Will Miles
//
// Shows how to redirect
//
#include <Arduino.h>
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
static AsyncWebServer server(80);
void setup() {
Serial.begin(115200);
#if ASYNCWEBSERVER_WIFI_SUPPORTED
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
// curl -v http://192.168.4.1/
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
request->redirect("/index.txt");
});
// curl -v http://192.168.4.1/index.txt
server.on("/index.txt", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "text/plain", "Hello, world!");
});
server.begin();
}
// not needed
void loop() {
delay(100);
}

View File

@@ -1,91 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2026 Hristo Gochkov, Mathieu Carbou, Emil Muratov, Will Miles
//
// Shows how to use request continuation to pause a request for a long processing task, and be able to resume it later.
//
#include <Arduino.h>
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
#include <list>
#include <mutex>
static AsyncWebServer server(80);
// request handler that is saved from the paused request to communicate with Serial
static String message;
static AsyncWebServerRequestPtr serialRequest;
// request handler that is saved from the paused request to communicate with GPIO
static uint8_t pin = 35;
static AsyncWebServerRequestPtr gpioRequest;
void setup() {
Serial.begin(115200);
#if ASYNCWEBSERVER_WIFI_SUPPORTED
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
// Post a message that will be sent to the Serial console, and pause the request until the user types a key
//
// curl -v -X POST -H "Content-Type: application/x-www-form-urlencoded" -d "question=Name%3F%20" http://192.168.4.1/serial
//
// curl output should show "Answer: [y/n]" as the response
server.on("/serial", HTTP_POST, [](AsyncWebServerRequest *request) {
message = request->getParam("question", true)->value();
serialRequest = request->pause();
});
// Wait for a GPIO to be high
//
// curl -v http://192.168.4.1/gpio
//
// curl output should show "GPIO is high!" as the response
server.on("/gpio", HTTP_GET, [](AsyncWebServerRequest *request) {
gpioRequest = request->pause();
});
pinMode(pin, INPUT);
server.begin();
}
void loop() {
delay(500);
// Check for a high voltage on the RX1 pin
if (digitalRead(pin) == HIGH) {
if (auto request = gpioRequest.lock()) {
request->send(200, "text/plain", "GPIO is high!");
}
}
// check for an incoming message from the Serial console
if (message.length()) {
Serial.printf("%s", message.c_str());
// drops buffer
while (Serial.available()) {
Serial.read();
}
Serial.setTimeout(10000);
String response = Serial.readStringUntil('\n'); // waits for a key to be pressed
Serial.println();
message = emptyString;
if (auto request = serialRequest.lock()) {
request->send(200, "text/plain", "Answer: " + response);
}
}
}

View File

@@ -1,165 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2026 Hristo Gochkov, Mathieu Carbou, Emil Muratov, Will Miles
//
// Shows how to use request continuation to pause a request for a long processing task, and be able to resume it later.
//
#include <Arduino.h>
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
#include <list>
#include <memory>
#include <mutex>
static AsyncWebServer server(80);
// ===============================================================
// The code below is used to simulate some long running operations
// ===============================================================
typedef struct {
size_t id;
AsyncWebServerRequestPtr requestPtr;
uint8_t data;
} LongRunningOperation;
static std::list<std::unique_ptr<LongRunningOperation>> longRunningOperations;
static size_t longRunningOperationsCount = 0;
#ifdef ESP32
static std::mutex longRunningOperationsMutex;
#endif
static void startLongRunningOperation(AsyncWebServerRequestPtr &&requestPtr) {
#ifdef ESP32
std::lock_guard<std::mutex> lock(longRunningOperationsMutex);
#endif
// LongRunningOperation *op = new LongRunningOperation();
std::unique_ptr<LongRunningOperation> op(new LongRunningOperation());
op->id = ++longRunningOperationsCount;
op->data = 10;
// you need to hold the AsyncWebServerRequestPtr returned by pause();
// This object is authorized to leave the scope of the request handler.
op->requestPtr = std::move(requestPtr);
Serial.printf("[%u] Start long running operation for %" PRIu8 " seconds...\n", op->id, op->data);
longRunningOperations.push_back(std::move(op));
}
static bool processLongRunningOperation(LongRunningOperation *op) {
// request was deleted ?
if (op->requestPtr.expired()) {
Serial.printf("[%u] Request was deleted - stopping long running operation\n", op->id);
return true; // operation finished
}
// processing the operation
Serial.printf("[%u] Long running operation processing... %" PRIu8 " seconds left\n", op->id, op->data);
// check if we have finished ?
op->data--;
if (op->data) {
// not finished yet
return false;
}
// Try to get access to the request pointer if it is still exist.
// If there has been a disconnection during that time, the pointer won't be valid anymore
if (auto request = op->requestPtr.lock()) {
Serial.printf("[%u] Long running operation finished! Sending back response...\n", op->id);
request->send(200, "text/plain", String(op->id) + " ");
} else {
Serial.printf("[%u] Long running operation finished, but request was deleted!\n", op->id);
}
return true; // operation finished
}
/// ==========================================================
void setup() {
Serial.begin(115200);
#if ASYNCWEBSERVER_WIFI_SUPPORTED
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
// Add a middleware to see how pausing a request affects the middleware chain
server.addMiddleware([](AsyncWebServerRequest *request, ArMiddlewareNext next) {
Serial.printf("Middleware chain start\n");
// continue to the next middleware, and at the end the request handler
next();
// we can check the request pause state after the handler was executed
if (request->isPaused()) {
Serial.printf("Request was paused!\n");
}
Serial.printf("Middleware chain ends\n");
});
// HOW TO RUN THIS EXAMPLE:
//
// 1. Open several terminals to trigger some requests concurrently that will be paused with:
// > time curl -v http://192.168.4.1/
//
// 2. Look at the output of the Serial console to see how the middleware chain is executed
// and to see the long running operations being processed and resume the requests.
//
// 3. You can try close your curl command to cancel the request and check that the request is deleted.
// Note: in case the network is disconnected, the request will be deleted.
//
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
// Print a message in case the request is disconnected (network disconnection, client close, etc.)
request->onDisconnect([]() {
Serial.printf("Request was disconnected!\n");
});
// Instruct ESPAsyncWebServer to pause the request and get a AsyncWebServerRequestPtr to be able to access the request later.
// The AsyncWebServerRequestPtr is the ONLY object authorized to leave the scope of the request handler.
// The Middleware chain will continue to run until the end after this handler exit, but the request will be paused and will not
// be sent to the client until send() is called later.
Serial.printf("Pausing request...\n");
AsyncWebServerRequestPtr requestPtr = request->pause();
// start our long operation...
startLongRunningOperation(std::move(requestPtr));
});
server.begin();
}
static uint32_t lastTime = 0;
void loop() {
if (millis() - lastTime >= 1000) {
#ifdef ESP32
Serial.printf("Free heap: %" PRIu32 "\n", ESP.getFreeHeap());
std::lock_guard<std::mutex> lock(longRunningOperationsMutex);
#endif
// process all long running operations
longRunningOperations.remove_if([](const std::unique_ptr<LongRunningOperation> &op) {
return processLongRunningOperation(op.get());
});
lastTime = millis();
}
}

View File

@@ -1,61 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2026 Hristo Gochkov, Mathieu Carbou, Emil Muratov, Will Miles
//
// Make sure resumable downloads can be implemented (HEAD request / response and Range header)
//
#include <Arduino.h>
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
static AsyncWebServer server(80);
void setup() {
Serial.begin(115200);
#if ASYNCWEBSERVER_WIFI_SUPPORTED
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
/*
curl -I -X HEAD http://192.168.4.1/download
HTTP/1.1 200 OK
Content-Length: 1024
Content-Type: application/octet-stream
Connection: close
Accept-Ranges: bytes
*/
// Ref: https://github.com/mathieucarbou/ESPAsyncWebServer/pull/80
server.on("/download", HTTP_HEAD | HTTP_GET, [](AsyncWebServerRequest *request) {
if (request->method() == HTTP_HEAD) {
AsyncWebServerResponse *response = request->beginResponse(200, "application/octet-stream");
response->addHeader(asyncsrv::T_Accept_Ranges, "bytes");
response->addHeader(asyncsrv::T_Content_Length, 10);
response->setContentLength(1024); // make sure we can overrides previously set content length
response->addHeader(asyncsrv::T_Content_Type, "foo");
response->setContentType("application/octet-stream"); // make sure we can overrides previously set content type
// ...
request->send(response);
} else {
// ...
}
});
server.begin();
}
void loop() {
delay(100);
}

View File

@@ -1,52 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2026 Hristo Gochkov, Mathieu Carbou, Emil Muratov, Will Miles
//
// Shows how to rewrite URLs
//
#include <Arduino.h>
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
static AsyncWebServer server(80);
void setup() {
Serial.begin(115200);
#if ASYNCWEBSERVER_WIFI_SUPPORTED
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
// curl -v http://192.168.4.1/index.txt
server.on("/index.txt", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "text/plain", "Hello, world!");
});
// curl -v http://192.168.4.1/index.txt
server.on("/index.html", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "text/html", "<h1>Hello, world!</h1>");
});
// curl -v http://192.168.4.1/
server.rewrite("/", "/index.html");
server.rewrite("/index.txt", "/index.html"); // will hide the .txt file
server.begin();
}
// not needed
void loop() {
delay(100);
}

View File

@@ -1,105 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2026 Hristo Gochkov, Mathieu Carbou, Emil Muratov, Will Miles
//
// SSE example
//
#include <Arduino.h>
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
static const char *htmlContent PROGMEM = R"(
<!DOCTYPE html>
<html>
<head>
<title>Server-Sent Events</title>
<script>
if (!!window.EventSource) {
var source = new EventSource('/events');
source.addEventListener('open', function(e) {
console.log("Events Connected");
}, false);
source.addEventListener('error', function(e) {
if (e.target.readyState != EventSource.OPEN) {
console.log("Events Disconnected");
}
}, false);
source.addEventListener('message', function(e) {
console.log("message", e.data);
}, false);
source.addEventListener('heartbeat', function(e) {
console.log("heartbeat", e.data);
}, false);
}
</script>
</head>
<body>
<h1>Open your browser console!</h1>
</body>
</html>
)";
static const size_t htmlContentLength = strlen_P(htmlContent);
static AsyncWebServer server(80);
static AsyncEventSource events("/events");
void setup() {
Serial.begin(115200);
#if ASYNCWEBSERVER_WIFI_SUPPORTED
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) {
Serial.printf("SSE Client connected!");
client->send("hello!", NULL, millis(), 1000);
});
events.onDisconnect([](AsyncEventSourceClient *client) {
Serial.printf("SSE Client disconnected!");
});
server.addHandler(&events);
server.begin();
}
static uint32_t lastSSE = 0;
static uint32_t deltaSSE = 3000;
static uint32_t lastHeap = 0;
void loop() {
uint32_t now = millis();
if (now - lastSSE >= deltaSSE) {
events.send(String("ping-") + now, "heartbeat", now);
lastSSE = millis();
}
#ifdef ESP32
if (now - lastHeap >= 2000) {
Serial.printf("Free heap: %" PRIu32 "\n", ESP.getFreeHeap());
lastHeap = now;
}
#endif
}

View File

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

View File

@@ -1,66 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2026 Hristo Gochkov, Mathieu Carbou, Emil Muratov, Will Miles
//
// Server state example
//
#include <Arduino.h>
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
static AsyncWebServer server1(80);
static AsyncWebServer server2(80);
void setup() {
Serial.begin(115200);
#if ASYNCWEBSERVER_WIFI_SUPPORTED
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
// server state returns one of the tcp_state enum values:
// enum tcp_state {
// CLOSED = 0,
// LISTEN = 1,
// SYN_SENT = 2,
// SYN_RCVD = 3,
// ESTABLISHED = 4,
// FIN_WAIT_1 = 5,
// FIN_WAIT_2 = 6,
// CLOSE_WAIT = 7,
// CLOSING = 8,
// LAST_ACK = 9,
// TIME_WAIT = 10
// };
assert(server1.state() == tcp_state::CLOSED);
assert(server2.state() == tcp_state::CLOSED);
server1.begin();
assert(server1.state() == tcp_state::LISTEN);
assert(server2.state() == tcp_state::CLOSED);
server2.begin();
assert(server1.state() == tcp_state::LISTEN);
assert(server2.state() == tcp_state::CLOSED);
Serial.println("Done!");
}
void loop() {
delay(100);
}

View File

@@ -1,73 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2026 Hristo Gochkov, Mathieu Carbou, Emil Muratov, Will Miles
//
// Authentication and authorization middlewares
//
#include <Arduino.h>
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
static AsyncWebServer server(80);
static AsyncAuthenticationMiddleware basicAuth;
static AsyncLoggingMiddleware logging;
void setup() {
Serial.begin(115200);
#if ASYNCWEBSERVER_WIFI_SUPPORTED
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
// basic authentication
basicAuth.setUsername("admin");
basicAuth.setPassword("admin");
basicAuth.setRealm("MyApp");
basicAuth.setAuthFailureMessage("Authentication failed");
basicAuth.setAuthType(AsyncAuthType::AUTH_BASIC);
basicAuth.generateHash(); // precompute hash (optional but recommended)
// logging middleware
logging.setEnabled(true);
logging.setOutput(Serial);
// we apply auth middleware to the server globally
server.addMiddleware(&basicAuth);
// protected endpoint: requires basic authentication
// curl -v -u admin:admin http://192.168.4.1/
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "text/plain", "Hello, world!");
});
// we skip all global middleware from the catchall handler
server.catchAllHandler().skipServerMiddlewares();
// we apply a specific middleware to the catchall handler only to log requests without a handler defined
server.catchAllHandler().addMiddleware(&logging);
// standard 404 handler: will display the request in the console i na curl-like style
// curl -v -H "Foo: Bar" http://192.168.4.1/foo
server.onNotFound([](AsyncWebServerRequest *request) {
request->send(404, "text/plain", "Not found");
});
server.begin();
}
// not needed
void loop() {
delay(100);
}

View File

@@ -1,157 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2026 Hristo Gochkov, Mathieu Carbou, Emil Muratov, Will Miles
//
// Simulate a slow response in a chunk response (like file download from SD Card)
// poll events will be throttled.
//
#include <Arduino.h>
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
static AsyncWebServer server(80);
static const char *htmlContent PROGMEM = R"(
<!DOCTYPE html>
<html>
<head>
<title>Sample HTML</title>
</head>
<body>
<h1>Hello, World!</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
</body>
</html>
)";
static const size_t htmlContentLength = strlen_P(htmlContent);
static constexpr char characters[] = "0123456789ABCDEF";
static size_t charactersIndex = 0;
void setup() {
Serial.begin(115200);
#if ASYNCWEBSERVER_WIFI_SUPPORTED
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);
});
// IMPORTANT - DO NOT WRITE SUCH CODE IN PRODUCTON !
//
// This example simulates the slowdown that can happen when:
// - downloading a huge file from sdcard
// - doing some file listing on SDCard because it is horribly slow to get a file listing with file stats on SDCard.
// So in both cases, ESP would deadlock or TWDT would trigger.
//
// This example simulats that by slowing down the chunk callback:
// - d=2000 is the delay in ms in the callback
// - l=10000 is the length of the response
//
// time curl -N -v -G -d 'd=2000' -d 'l=10000' http://192.168.4.1/slow.html --output -
//
// THIS CODE WILL CRASH BECAUSE OF THE WATCHDOG.
// IF YOU REALLY NEED TO DO THIS, YOU MUST DISABLE THE TWDT
//
// CORRECT WAY IS TO USE SSE OR WEBSOCKETS TO DO THE COSTLY PROCESSING ASYNC.
//
server.on("/slow.html", HTTP_GET, [](AsyncWebServerRequest *request) {
uint32_t d = request->getParam("d")->value().toInt();
uint32_t l = request->getParam("l")->value().toInt();
Serial.printf("d = %" PRIu32 ", l = %" PRIu32 "\n", d, l);
AsyncWebServerResponse *response = request->beginChunkedResponse("text/html", [d, l](uint8_t *buffer, size_t maxLen, size_t index) -> size_t {
Serial.printf("%u\n", index);
// finished ?
if (index >= l) {
return 0;
}
// slow down the task to simulate some heavy processing, like SD card reading
delay(d);
memset(buffer, characters[charactersIndex], 256);
charactersIndex = (charactersIndex + 1) % sizeof(characters);
return 256;
});
request->send(response);
});
server.begin();
}
static uint32_t lastHeap = 0;
void loop() {
#ifdef ESP32
uint32_t now = millis();
if (now - lastHeap >= 2000) {
Serial.printf("Free heap: %" PRIu32 "\n", ESP.getFreeHeap());
lastHeap = now;
}
#endif
}

View File

@@ -1,178 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2026 Hristo Gochkov, Mathieu Carbou, Emil Muratov, Will Miles
//
// Shows how to serve a static file
//
#include <Arduino.h>
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
#include <LittleFS.h>
static AsyncWebServer server(80);
static const char *htmlContent PROGMEM = R"(
<!DOCTYPE html>
<html>
<head>
<title>Sample HTML</title>
</head>
<body>
<h1>Hello, World!</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
</body>
</html>
)";
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);
#if ASYNCWEBSERVER_WIFI_SUPPORTED
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
#ifdef ESP32
LittleFS.begin(true);
#else
LittleFS.begin();
#endif
{
File f = LittleFS.open("/index.html", "w");
assert(f);
f.print(htmlContent);
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");
{
File f = LittleFS.open("/files/a.txt", "w");
assert(f);
f.print("Hello from a.txt");
f.close();
}
{
File f = LittleFS.open("/files/b.txt", "w");
assert(f);
f.print("Hello from b.txt");
f.close();
}
// curl -v http://192.168.4.1/
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
request->redirect("/index.html");
});
// 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
// curl -v http://192.168.4.1/base/b.txt => serves b.txt
server.serveStatic("/base", LittleFS, "/files").setDefaultFile("a.txt");
server.begin();
}
// not needed
void loop() {
delay(100);
}

View File

@@ -1,152 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2026 Hristo Gochkov, Mathieu Carbou, Emil Muratov, Will Miles
//
// Shows how to serve a static and dynamic template
//
#include <Arduino.h>
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
#include <LittleFS.h>
static AsyncWebServer server(80);
static const char *htmlContent PROGMEM = R"(
<!DOCTYPE html>
<html>
<body>
<h1>Hello, %USER%</h1>
</body>
</html>
)";
static const size_t htmlContentLength = strlen_P(htmlContent);
// Variables used for dynamic cacheable template
static unsigned uptimeInMinutes = 0;
static AsyncStaticWebHandler *uptimeHandler = nullptr;
// Utility function for performing that update
static void setUptimeInMinutes(unsigned t) {
uptimeInMinutes = t;
// Update caching header with a new value as well
if (uptimeHandler) {
uptimeHandler->setLastModified();
}
}
void setup() {
Serial.begin(115200);
#if ASYNCWEBSERVER_WIFI_SUPPORTED
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
#ifdef ESP32
LittleFS.begin(true);
#else
LittleFS.begin();
#endif
{
File f = LittleFS.open("/template.html", "w");
assert(f);
f.print(htmlContent);
f.close();
}
// Serve the static template file
//
// This call will have caching headers automatically added as it is a static file.
//
// curl -v http://192.168.4.1/template.html
server.serveStatic("/template.html", LittleFS, "/template.html");
// Serve a template with dynamic content
//
// serveStatic recognizes that template processing is in use, and will not automatically
// add caching headers.
//
// curl -v http://192.168.4.1/dynamic.html
server.serveStatic("/dynamic.html", LittleFS, "/template.html").setTemplateProcessor([](const String &var) -> String {
if (var == "USER") {
return String("Bob ") + millis();
}
return emptyString;
});
// Serve a static template with a template processor
//
// By explicitly calling setLastModified() on the handler object, we enable
// sending the caching headers, even when a template is in use.
// This pattern should never be used with template data that can change.
// Example below: USER never changes.
//
// curl -v http://192.168.4.1/index.html
server.serveStatic("/index.html", LittleFS, "/template.html")
.setTemplateProcessor([](const String &var) -> String {
if (var == "USER") {
return "Bob";
}
return emptyString;
})
.setLastModified("Sun, 28 Sep 2025 01:02:03 GMT");
// Serve a template with dynamic content *and* caching
//
// The data used in this template is updated in loop(). loop() is then responsible
// for calling setLastModified() on the handler object to notify any caches that
// the data has changed.
//
// curl -v http://192.168.4.1/uptime.html
uptimeHandler = &server.serveStatic("/uptime.html", LittleFS, "/template.html").setTemplateProcessor([](const String &var) -> String {
if (var == "USER") {
return String("Bob ") + uptimeInMinutes + " minutes";
}
return emptyString;
});
// Serve a template with dynamic content based on user request
//
// In this case, the template is served via a callback request. Data from the request
// is used to generate the template callback.
//
// curl -v -G -d "USER=Bob" http://192.168.4.1/user_request.html
server.on("/user_request.html", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(LittleFS, "/template.html", "text/html", false, [=](const String &var) -> String {
if (var == "USER") {
const AsyncWebParameter *param = request->getParam("USER");
if (param) {
return param->value();
}
}
return emptyString;
});
});
server.begin();
}
// not needed
void loop() {
delay(100);
// Compute uptime
unsigned currentUptimeInMinutes = millis() / (60 * 1000);
if (currentUptimeInMinutes != uptimeInMinutes) {
setUptimeInMinutes(currentUptimeInMinutes);
}
}

View File

@@ -1,349 +0,0 @@
# AsyncURIMatcher Example
This example demonstrates the comprehensive URI matching capabilities of the ESPAsyncWebServer library using the `AsyncURIMatcher` class.
## Overview
The `AsyncURIMatcher` class provides flexible and powerful URL routing mechanisms that go beyond simple string matching. It supports various matching strategies that can be combined to create sophisticated routing rules.
**Important**: When using plain strings (not `AsyncURIMatcher` objects), the library uses auto-detection (`URIMatchAuto`) which analyzes the URI pattern and applies appropriate matching rules. This is **not** simple exact matching - it combines exact and folder matching by default!
## What's Demonstrated
This example includes two Arduino sketches:
1. **URIMatcher.ino** - Interactive web-based demonstration with a user-friendly homepage
2. **URIMatcherTest.ino** - Comprehensive test suite with automated shell script testing
Both sketches create a WiFi Access Point (`esp-captive`) for easy testing without network configuration.
## Auto-Detection Behavior
When you pass a plain string or `const char*` to `server.on()`, the `URIMatchAuto` flag is used, which:
1. **Empty URI**: Matches everything
2. **Ends with `*`**: Becomes prefix match (`URIMatchPrefix`)
3. **Contains `/*.ext`**: Becomes extension match (`URIMatchExtension`)
4. **Starts with `^` and ends with `$`**: Becomes regex match (if enabled)
5. **Everything else**: Becomes **both** exact and folder match (`URIMatchPrefixFolder | URIMatchExact`)
This means traditional string-based routes like `server.on("/path", handler)` will match:
- `/path` (exact match)
- `/path/` (folder with trailing slash)
- `/path/anything` (folder match)
But will **NOT** match `/path-suffix` (prefix without folder separator).
## Features Demonstrated
### 1. **Auto-Detection (Traditional Behavior)**
Demonstrates how traditional string-based routing automatically combines exact and folder matching.
**Examples in URIMatcher.ino:**
- `/auto` - Matches both `/auto` exactly AND `/auto/sub` as folder
- `/wildcard*` - Auto-detects as prefix match (due to trailing `*`)
- `/auto-images/*.png` - Auto-detects as extension match (due to `/*.ext` pattern)
**Examples in URIMatcherTest.ino:**
- `/exact` - Matches `/exact`, `/exact/`, and `/exact/sub`
- `/api/users` - Matches exact path and subpaths under `/api/users/`
- `/*.json` - Matches any `.json` file anywhere
- `/*.css` - Matches any `.css` file anywhere
### 2. **Exact Matching (Factory Method)**
Using `AsyncURIMatcher::exact()` matches only the exact URL, **NOT** subpaths.
**Key difference from auto-detection:** `AsyncURIMatcher::exact("/path")` matches **only** `/path`, while `server.on("/path", ...)` matches both `/path` and `/path/sub`.
**Examples in URIMatcher.ino:**
- `AsyncURIMatcher::exact("/exact")` - Matches only `/exact`
**Examples in URIMatcherTest.ino:**
- `AsyncURIMatcher::exact("/factory/exact")` - Matches only `/factory/exact`
- Does NOT match `/factory/exact/sub` (404 response)
### 3. **Prefix Matching**
Using `AsyncURIMatcher::prefix()` matches URLs that start with the specified pattern.
**Examples in URIMatcher.ino:**
- `AsyncURIMatcher::prefix("/service")` - Matches `/service`, `/service-test`, `/service/status`
**Examples in URIMatcherTest.ino:**
- `AsyncURIMatcher::prefix("/factory/prefix")` - Matches `/factory/prefix`, `/factory/prefix-test`, `/factory/prefix/sub`
- Traditional: `/api/*` - Matches `/api/data`, `/api/v1/posts`
- Traditional: `/files/*` - Matches `/files/document.pdf`, `/files/images/photo.jpg`
### 4. **Folder/Directory Matching**
Using `AsyncURIMatcher::dir()` matches URLs under a directory (automatically adds trailing slash).
**Important:** Directory matching requires a trailing slash in the URL - it does NOT match the directory itself.
**Examples in URIMatcher.ino:**
- `AsyncURIMatcher::dir("/admin")` - Matches `/admin/users`, `/admin/settings`
- Does NOT match `/admin` without trailing slash
**Examples in URIMatcherTest.ino:**
- `AsyncURIMatcher::dir("/factory/dir")` - Matches `/factory/dir/users`, `/factory/dir/sub/path`
- Does NOT match `/factory/dir` itself (404 response)
### 5. **Extension Matching**
Using `AsyncURIMatcher::ext()` matches files with specific extensions.
**Examples in URIMatcher.ino:**
- `AsyncURIMatcher::ext("/images/*.jpg")` - Matches `/images/photo.jpg`, `/images/sub/pic.jpg`
**Examples in URIMatcherTest.ino:**
- `AsyncURIMatcher::ext("/factory/files/*.txt")` - Matches `/factory/files/doc.txt`, `/factory/files/sub/readme.txt`
- Does NOT match `/factory/files/doc.pdf` (wrong extension)
### 6. **Case Insensitive Matching**
Using `AsyncURIMatcher::CaseInsensitive` flag matches URLs regardless of character case.
**Examples in URIMatcher.ino:**
- `AsyncURIMatcher::exact("/case", AsyncURIMatcher::CaseInsensitive)` - Matches `/case`, `/CASE`, `/CaSe`
**Examples in URIMatcherTest.ino:**
- Case insensitive exact: `/case/exact`, `/CASE/EXACT`, `/Case/Exact` all work
- Case insensitive prefix: `/case/prefix`, `/CASE/PREFIX-test`, `/Case/Prefix/sub` all work
- Case insensitive directory: `/case/dir/users`, `/CASE/DIR/admin`, `/Case/Dir/settings` all work
- Case insensitive extension: `/case/files/doc.pdf`, `/CASE/FILES/DOC.PDF`, `/Case/Files/Doc.Pdf` all work
### 7. **Regular Expression Matching**
Using `AsyncURIMatcher::regex()` for advanced pattern matching (requires `ASYNCWEBSERVER_REGEX`).
**Examples in URIMatcher.ino:**
```cpp
#ifdef ASYNCWEBSERVER_REGEX
AsyncURIMatcher::regex("^/user/([0-9]+)$") // Matches /user/123, captures ID
#endif
```
**Examples in URIMatcherTest.ino:**
- Traditional regex: `^/user/([0-9]+)$` - Matches `/user/123`, `/user/456`
- Traditional regex: `^/blog/([0-9]{4})/([0-9]{2})/([0-9]{2})$` - Matches `/blog/2023/10/15`
- Factory regex: `AsyncURIMatcher::regex("^/factory/user/([0-9]+)$")` - Matches `/factory/user/123`
- Factory regex with multiple captures: `^/factory/blog/([0-9]{4})/([0-9]{2})/([0-9]{2})$`
- Case insensitive regex: `AsyncURIMatcher::regex("^/factory/search/(.+)$", AsyncURIMatcher::CaseInsensitive)`
### 8. **Combined Flags**
Multiple matching strategies can be combined using the `|` operator.
**Examples in URIMatcher.ino:**
- `AsyncURIMatcher::prefix("/MixedCase", AsyncURIMatcher::CaseInsensitive)` - Prefix match that's case insensitive
### 9. **Special Matchers**
**Examples in URIMatcherTest.ino:**
- `AsyncURIMatcher::all()` - Matches all requests (used with POST method as catch-all)
## Usage Patterns
### Traditional String-based Routing (Auto-Detection)
```cpp
// Auto-detection with exact + folder matching
server.on("/api", handler); // Matches /api AND /api/anything
server.on("/login", handler); // Matches /login AND /login/sub
// Auto-detection with prefix matching
server.on("/prefix*", handler); // Matches /prefix, /prefix-test, /prefix/sub
// Auto-detection with extension matching
server.on("/images/*.jpg", handler); // Matches /images/pic.jpg, /images/sub/pic.jpg
```
### Explicit AsyncURIMatcher Syntax
### Explicit AsyncURIMatcher Syntax
```cpp
// Exact matching only
server.on(AsyncURIMatcher("/path", URIMatchExact), handler);
// Prefix matching only
server.on(AsyncURIMatcher("/api", URIMatchPrefix), handler);
// Combined flags
server.on(AsyncURIMatcher("/api", URIMatchPrefix | URIMatchCaseInsensitive), handler);
```
### Factory Functions
```cpp
// More readable and expressive
server.on(AsyncURIMatcher::exact("/login"), handler);
server.on(AsyncURIMatcher::prefix("/api"), handler);
server.on(AsyncURIMatcher::dir("/admin"), handler);
server.on(AsyncURIMatcher::ext("/images/*.jpg"), handler);
#ifdef ASYNCWEBSERVER_REGEX
server.on(AsyncURIMatcher::regex("^/user/([0-9]+)$"), handler);
#endif
```
## Available Flags
| Flag | Description |
| ------------------------- | ----------------------------------------------------------- |
| `URIMatchAuto` | Auto-detect match type from pattern (default) |
| `URIMatchExact` | Exact URL match |
| `URIMatchPrefix` | Prefix match |
| `URIMatchPrefixFolder` | Folder prefix match (requires trailing /) |
| `URIMatchExtension` | File extension match pattern |
| `URIMatchCaseInsensitive` | Case insensitive matching |
| `URIMatchRegex` | Regular expression matching (requires ASYNCWEBSERVER_REGEX) |
## Testing the Example
1. **Upload the sketch** to your ESP32/ESP8266
2. **Connect to WiFi AP**: `esp-captive` (no password required)
3. **Navigate to**: `http://192.168.4.1/`
4. **Explore the examples** by clicking the organized test links
5. **Monitor Serial output**: Open Serial Monitor to see detailed debugging information for each matched route
### Test URLs Available (All Clickable from Homepage)
**Auto-Detection Examples:**
- `http://192.168.4.1/auto` (exact + folder match)
- `http://192.168.4.1/auto/sub` (folder match - same handler!)
- `http://192.168.4.1/wildcard-test` (auto-detected prefix)
- `http://192.168.4.1/auto-images/photo.png` (auto-detected extension)
**Factory Method Examples:**
- `http://192.168.4.1/exact` (AsyncURIMatcher::exact)
- `http://192.168.4.1/service/status` (AsyncURIMatcher::prefix)
- `http://192.168.4.1/admin/users` (AsyncURIMatcher::dir)
- `http://192.168.4.1/images/photo.jpg` (AsyncURIMatcher::ext)
**Case Insensitive Examples:**
- `http://192.168.4.1/case` (lowercase)
- `http://192.168.4.1/CASE` (uppercase)
- `http://192.168.4.1/CaSe` (mixed case)
**Regex Examples (if ASYNCWEBSERVER_REGEX enabled):**
- `http://192.168.4.1/user/123` (captures numeric ID)
- `http://192.168.4.1/user/456` (captures numeric ID)
**Combined Flags Examples:**
- `http://192.168.4.1/mixedcase-test` (prefix + case insensitive)
- `http://192.168.4.1/MIXEDCASE/sub` (prefix + case insensitive)
### Console Output
Each handler provides detailed debugging information via Serial output:
```
Auto-Detection Match (Traditional)
Matched URL: /auto
Uses auto-detection: exact + folder matching
```
```
Factory Exact Match
Matched URL: /exact
Uses AsyncURIMatcher::exact() factory function
```
```
Regex Match - User ID
Matched URL: /user/123
Captured User ID: 123
This regex matches /user/{number} pattern
```
## Compilation Options
### Enable Regex Support
To enable regular expression matching, compile with:
```
-D ASYNCWEBSERVER_REGEX
```
In PlatformIO, add to `platformio.ini`:
```ini
build_flags = -D ASYNCWEBSERVER_REGEX
```
In Arduino IDE, add to your sketch:
```cpp
#define ASYNCWEBSERVER_REGEX
```
## Performance Considerations
1. **Exact matches** are fastest
2. **Prefix matches** are very efficient
3. **Regex matches** are slower but most flexible
4. **Case insensitive** matching adds minimal overhead
5. **Auto-detection** adds slight parsing overhead at construction time
## Real-World Applications
### REST API Design
```cpp
// API versioning
server.on(AsyncURIMatcher::prefix("/api/v1"), handleAPIv1);
server.on(AsyncURIMatcher::prefix("/api/v2"), handleAPIv2);
// Resource endpoints with IDs
server.on(AsyncURIMatcher::regex("^/api/users/([0-9]+)$"), handleUserById);
server.on(AsyncURIMatcher::regex("^/api/posts/([0-9]+)/comments$"), handlePostComments);
```
### File Serving
```cpp
// Serve different file types
server.on(AsyncURIMatcher::ext("/assets/*.css"), serveCSSFiles);
server.on(AsyncURIMatcher::ext("/assets/*.js"), serveJSFiles);
server.on(AsyncURIMatcher::ext("/images/*.jpg"), serveImageFiles);
```
### Admin Interface
```cpp
// Admin section with authentication
server.on(AsyncURIMatcher::dir("/admin"), handleAdminPages);
server.on(AsyncURIMatcher::exact("/admin"), redirectToAdminDashboard);
```
## See Also
- [ESPAsyncWebServer Documentation](https://github.com/ESP32Async/ESPAsyncWebServer)
- [Regular Expression Reference](https://en.cppreference.com/w/cpp/regex)
- Other examples in the `examples/` directory

View File

@@ -1,276 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2026 Hristo Gochkov, Mathieu Carbou, Emil Muratov, Will Miles
//
// AsyncURIMatcher Examples - Advanced URI Matching and Routing
//
// This example demonstrates the various ways to use AsyncURIMatcher class
// for flexible URL routing with different matching strategies:
//
// 1. Exact matching
// 2. Prefix matching
// 3. Folder/directory matching
// 4. Extension matching
// 5. Case insensitive matching
// 6. Regex matching (if ASYNCWEBSERVER_REGEX is enabled)
// 7. Factory functions for common patterns
//
// Test URLs:
// - Exact: http://192.168.4.1/exact
// - Prefix: http://192.168.4.1/prefix-anything
// - Folder: http://192.168.4.1/api/users, http://192.168.4.1/api/posts
// - Extension: http://192.168.4.1/images/photo.jpg, http://192.168.4.1/docs/readme.pdf
// - Case insensitive: http://192.168.4.1/CaSe or http://192.168.4.1/case
// - Wildcard: http://192.168.4.1/wildcard-test
#include <Arduino.h>
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
static AsyncWebServer server(80);
void setup() {
Serial.begin(115200);
Serial.println();
Serial.println("=== AsyncURIMatcher Example ===");
#if ASYNCWEBSERVER_WIFI_SUPPORTED
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
Serial.print("AP IP address: ");
Serial.println(WiFi.softAPIP());
#endif
// =============================================================================
// 1. AUTO-DETECTION BEHAVIOR - traditional string-based routing
// =============================================================================
// Traditional string-based routing with auto-detection
// This uses URIMatchAuto which combines URIMatchPrefixFolder | URIMatchExact
// It will match BOTH "/auto" exactly AND "/auto/" + anything
server.on("/auto", HTTP_GET, [](AsyncWebServerRequest *request) {
Serial.println("Auto-Detection Match (Traditional)");
Serial.println("Matched URL: " + request->url());
Serial.println("Uses auto-detection: exact + folder matching");
request->send(200, "text/plain", "OK - Auto-detection match");
});
// Auto-detection for wildcard patterns (ends with *)
// This auto-detects as URIMatchPrefix
server.on("/wildcard*", HTTP_GET, [](AsyncWebServerRequest *request) {
Serial.println("Auto-Detected Wildcard (Prefix)");
Serial.println("Matched URL: " + request->url());
Serial.println("Auto-detected as prefix match due to trailing *");
request->send(200, "text/plain", "OK - Wildcard prefix match");
});
// Auto-detection for extension patterns (contains /*.ext)
// This auto-detects as URIMatchExtension
server.on("/auto-images/*.png", HTTP_GET, [](AsyncWebServerRequest *request) {
Serial.println("Auto-Detected Extension Pattern");
Serial.println("Matched URL: " + request->url());
Serial.println("Auto-detected as extension match due to /*.png pattern");
request->send(200, "text/plain", "OK - Extension match");
});
// =============================================================================
// 2. EXACT MATCHING - matches only the exact URL (explicit)
// =============================================================================
// Using factory function for exact match
server.on(AsyncURIMatcher::exact("/exact"), HTTP_GET, [](AsyncWebServerRequest *request) {
Serial.println("Factory Exact Match");
Serial.println("Matched URL: " + request->url());
Serial.println("Uses AsyncURIMatcher::exact() factory function");
request->send(200, "text/plain", "OK - Factory exact match");
});
// =============================================================================
// 3. PREFIX MATCHING - matches URLs that start with the pattern
// =============================================================================
// Using factory function for prefix match
server.on(AsyncURIMatcher::prefix("/service"), HTTP_GET, [](AsyncWebServerRequest *request) {
Serial.println("Service Prefix Match (Factory)");
Serial.println("Matched URL: " + request->url());
Serial.println("Uses AsyncURIMatcher::prefix() factory function");
request->send(200, "text/plain", "OK - Factory prefix match");
});
// =============================================================================
// 4. FOLDER/DIRECTORY MATCHING - matches URLs in a folder structure
// =============================================================================
// Folder match using factory function (automatically adds trailing slash)
server.on(AsyncURIMatcher::dir("/admin"), HTTP_GET, [](AsyncWebServerRequest *request) {
Serial.println("Admin Directory Match");
Serial.println("Matched URL: " + request->url());
Serial.println("This matches URLs under /admin/ directory");
Serial.println("Note: /admin (without slash) will NOT match");
request->send(200, "text/plain", "OK - Directory match");
});
// =============================================================================
// 5. EXTENSION MATCHING - matches files with specific extensions
// =============================================================================
// Image extension matching
server.on(AsyncURIMatcher::ext("/images/*.jpg"), HTTP_GET, [](AsyncWebServerRequest *request) {
Serial.println("JPG Image Handler");
Serial.println("Matched URL: " + request->url());
Serial.println("This matches any .jpg file under /images/");
request->send(200, "text/plain", "OK - Extension match");
});
// =============================================================================
// 6. CASE INSENSITIVE MATCHING
// =============================================================================
// Case insensitive exact match
server.on(AsyncURIMatcher::exact("/case", AsyncURIMatcher::CaseInsensitive), HTTP_GET, [](AsyncWebServerRequest *request) {
Serial.println("Case Insensitive Match");
Serial.println("Matched URL: " + request->url());
Serial.println("This matches /case in any case combination");
request->send(200, "text/plain", "OK - Case insensitive match");
});
#ifdef ASYNCWEBSERVER_REGEX
// =============================================================================
// 7. REGEX MATCHING (only available if ASYNCWEBSERVER_REGEX is enabled)
// =============================================================================
// Regex match for numeric IDs
server.on(AsyncURIMatcher::regex("^/user/([0-9]+)$"), HTTP_GET, [](AsyncWebServerRequest *request) {
Serial.println("Regex Match - User ID");
Serial.println("Matched URL: " + request->url());
if (request->pathArg(0).length() > 0) {
Serial.println("Captured User ID: " + request->pathArg(0));
}
Serial.println("This regex matches /user/{number} pattern");
request->send(200, "text/plain", "OK - Regex match");
});
#endif
// =============================================================================
// 8. COMBINED FLAGS EXAMPLE
// =============================================================================
// Combine multiple flags
server.on(AsyncURIMatcher::prefix("/MixedCase", AsyncURIMatcher::CaseInsensitive), HTTP_GET, [](AsyncWebServerRequest *request) {
Serial.println("Combined Flags Example");
Serial.println("Matched URL: " + request->url());
Serial.println("Uses both AsyncURIMatcher::Prefix and AsyncURIMatcher::CaseInsensitive");
request->send(200, "text/plain", "OK - Combined flags match");
});
// =============================================================================
// 9. HOMEPAGE WITH NAVIGATION
// =============================================================================
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
Serial.println("Homepage accessed");
String response = R"(<!DOCTYPE html>
<html>
<head>
<title>AsyncURIMatcher Examples</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
h1 { color: #2E86AB; }
.test-link { display: block; margin: 5px 0; padding: 8px; background: #f0f0f0; text-decoration: none; color: #333; border-radius: 4px; }
.test-link:hover { background: #e0e0e0; }
.section { margin: 20px 0; }
</style>
</head>
<body>
<h1>AsyncURIMatcher Examples</h1>
<div class="section">
<h3>Auto-Detection (Traditional String Matching)</h3>
<a href="/auto" class="test-link">/auto (auto-detection: exact + folder)</a>
<a href="/auto/sub" class="test-link">/auto/sub (folder match)</a>
<a href="/wildcard-test" class="test-link">/wildcard-test (auto prefix)</a>
<a href="/auto-images/photo.png" class="test-link">/auto-images/photo.png (auto extension)</a>
</div>
<div class="section">
<h3>Factory Method Examples</h3>
<a href="/exact" class="test-link">/exact (factory exact)</a>
<a href="/service/status" class="test-link">/service/status (factory prefix)</a>
<a href="/admin/users" class="test-link">/admin/users (factory directory)</a>
<a href="/images/photo.jpg" class="test-link">/images/photo.jpg (factory extension)</a>
</div>
<div class="section">
<h3>Case Insensitive Matching</h3>
<a href="/case" class="test-link">/case (lowercase)</a>
<a href="/CASE" class="test-link">/CASE (uppercase)</a>
<a href="/CaSe" class="test-link">/CaSe (mixed case)</a>
</div>
)";
#ifdef ASYNCWEBSERVER_REGEX
response += R"( <div class="section">
<h3>Regex Matching</h3>
<a href="/user/123" class="test-link">/user/123 (regex numeric ID)</a>
<a href="/user/456" class="test-link">/user/456 (regex numeric ID)</a>
</div>
)";
#endif
response += R"( <div class="section">
<h3>Combined Flags</h3>
<a href="/mixedcase-test" class="test-link">/mixedcase-test (prefix + case insensitive)</a>
<a href="/MIXEDCASE/sub" class="test-link">/MIXEDCASE/sub (prefix + case insensitive)</a>
</div>
</body>
</html>)";
request->send(200, "text/html", response);
});
// =============================================================================
// 10. NOT FOUND HANDLER
// =============================================================================
server.onNotFound([](AsyncWebServerRequest *request) {
String html = "<h1>404 - Not Found</h1>";
html += "<p>The requested URL <strong>" + request->url() + "</strong> was not found.</p>";
html += "<p><a href='/'>← Back to Examples</a></p>";
request->send(404, "text/html", html);
});
server.begin();
Serial.println();
Serial.println("=== Server Started ===");
Serial.println("Open your browser and navigate to:");
Serial.println("http://192.168.4.1/ - Main examples page");
Serial.println();
Serial.println("Available test endpoints:");
Serial.println("• Auto-detection: /auto (exact+folder), /wildcard*, /auto-images/*.png");
Serial.println("• Exact matches: /exact");
Serial.println("• Prefix matches: /service*");
Serial.println("• Folder matches: /admin/*");
Serial.println("• Extension matches: /images/*.jpg");
Serial.println("• Case insensitive: /case (try /CASE, /Case)");
#ifdef ASYNCWEBSERVER_REGEX
Serial.println("• Regex matches: /user/123");
#endif
Serial.println("• Combined flags: /mixedcase*");
Serial.println();
}
void loop() {
// Nothing to do here - the server handles everything asynchronously
delay(1000);
}

View File

@@ -1,165 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2026 Hristo Gochkov, Mathieu Carbou, Emil Muratov, Will Miles
//
// Test for ESPAsyncWebServer URI matching
//
// Usage: upload, connect to the AP and run test_routes.sh
//
#include <Arduino.h>
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
AsyncWebServer server(80);
void setup() {
Serial.begin(115200);
#if ASYNCWEBSERVER_WIFI_SUPPORTED
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
// Status endpoint
server.on("/status", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "text/plain", "OK");
});
// Exact paths, plus the subpath (/exact matches /exact/sub but not /exact-no-match)
server.on("/exact", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "text/plain", "OK");
});
server.on("/api/users", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "text/plain", "OK");
});
// Prefix matching
server.on("/api/*", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "text/plain", "OK");
});
server.on("/files/*", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "text/plain", "OK");
});
// Extensions
server.on("/*.json", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "application/json", "{\"status\":\"OK\"}");
});
server.on("/*.css", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "text/css", "/* OK */");
});
// =============================================================================
// NEW ASYNCURIMATCHER FACTORY METHODS TESTS
// =============================================================================
// Exact match using factory method (does NOT match subpaths like traditional)
server.on(AsyncURIMatcher::exact("/factory/exact"), HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "text/plain", "OK");
});
// Prefix match using factory method
server.on(AsyncURIMatcher::prefix("/factory/prefix"), HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "text/plain", "OK");
});
// Directory match using factory method (matches /dir/anything but not /dir itself)
server.on(AsyncURIMatcher::dir("/factory/dir"), HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "text/plain", "OK");
});
// Extension match using factory method
server.on(AsyncURIMatcher::ext("/factory/files/*.txt"), HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "text/plain", "OK");
});
// =============================================================================
// CASE INSENSITIVE MATCHING TESTS
// =============================================================================
// Case insensitive exact match
server.on(AsyncURIMatcher::exact("/case/exact", AsyncURIMatcher::CaseInsensitive), HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "text/plain", "OK");
});
// Case insensitive prefix match
server.on(AsyncURIMatcher::prefix("/case/prefix", AsyncURIMatcher::CaseInsensitive), HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "text/plain", "OK");
});
// Case insensitive directory match
server.on(AsyncURIMatcher::dir("/case/dir", AsyncURIMatcher::CaseInsensitive), HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "text/plain", "OK");
});
// Case insensitive extension match
server.on(AsyncURIMatcher::ext("/case/files/*.PDF", AsyncURIMatcher::CaseInsensitive), HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "text/plain", "OK");
});
#ifdef ASYNCWEBSERVER_REGEX
// Traditional regex patterns (backward compatibility)
server.on("^/user/([0-9]+)$", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "text/plain", "OK");
});
server.on("^/blog/([0-9]{4})/([0-9]{2})/([0-9]{2})$", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "text/plain", "OK");
});
// =============================================================================
// NEW ASYNCURIMATCHER REGEX FACTORY METHODS
// =============================================================================
// Regex match using factory method
server.on(AsyncURIMatcher::regex("^/factory/user/([0-9]+)$"), HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "text/plain", "OK");
});
// Case insensitive regex match using factory method
server.on(AsyncURIMatcher::regex("^/factory/search/(.+)$", AsyncURIMatcher::CaseInsensitive), HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "text/plain", "OK");
});
// Complex regex with multiple capture groups
server.on(AsyncURIMatcher::regex("^/factory/blog/([0-9]{4})/([0-9]{2})/([0-9]{2})$"), HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "text/plain", "OK");
});
#endif
// =============================================================================
// SPECIAL MATCHERS
// =============================================================================
// Match all POST requests (catch-all before 404)
server.on(AsyncURIMatcher::all(), HTTP_POST, [](AsyncWebServerRequest *request) {
request->send(200, "text/plain", "OK");
});
// 404 handler
server.onNotFound([](AsyncWebServerRequest *request) {
request->send(404, "text/plain", "Not Found");
});
server.begin();
Serial.println("Server ready");
}
// not needed
void loop() {
delay(100);
}

View File

@@ -1,174 +0,0 @@
#!/bin/bash
# URI Matcher Test Script
# Tests all routes defined in URIMatcherTest.ino
SERVER_IP="${1:-192.168.4.1}"
SERVER_PORT="80"
BASE_URL="http://${SERVER_IP}:${SERVER_PORT}"
echo "Testing URI Matcher at $BASE_URL"
echo "=================================="
# Function to test a route
test_route() {
local path="$1"
local expected_status="$2"
local description="$3"
echo -n "Testing $path ... "
response=$(curl -s -w "HTTPSTATUS:%{http_code}" "$BASE_URL$path" 2>/dev/null)
status_code=$(echo "$response" | grep -o "HTTPSTATUS:[0-9]*" | cut -d: -f2)
if [ "$status_code" = "$expected_status" ]; then
echo "✅ PASS ($status_code)"
else
echo "❌ FAIL (expected $expected_status, got $status_code)"
return 1
fi
return 0
}
# Test counter
PASS=0
FAIL=0
# Test all routes that should return 200 OK
echo "Testing routes that should work (200 OK):"
if test_route "/status" "200" "Status endpoint"; then ((PASS++)); else ((FAIL++)); fi
if test_route "/exact" "200" "Exact path"; then ((PASS++)); else ((FAIL++)); fi
if test_route "/exact/" "200" "Exact path ending with /"; then ((PASS++)); else ((FAIL++)); fi
if test_route "/exact/sub" "200" "Exact path with subpath /"; then ((PASS++)); else ((FAIL++)); fi
if test_route "/api/users" "200" "Exact API path"; then ((PASS++)); else ((FAIL++)); fi
if test_route "/api/data" "200" "API prefix match"; then ((PASS++)); else ((FAIL++)); fi
if test_route "/api/v1/posts" "200" "API prefix deep"; then ((PASS++)); else ((FAIL++)); fi
if test_route "/files/document.pdf" "200" "Files prefix"; then ((PASS++)); else ((FAIL++)); fi
if test_route "/files/images/photo.jpg" "200" "Files prefix deep"; then ((PASS++)); else ((FAIL++)); fi
if test_route "/config.json" "200" "JSON extension"; then ((PASS++)); else ((FAIL++)); fi
if test_route "/data/settings.json" "200" "JSON extension in subfolder"; then ((PASS++)); else ((FAIL++)); fi
if test_route "/style.css" "200" "CSS extension"; then ((PASS++)); else ((FAIL++)); fi
if test_route "/assets/main.css" "200" "CSS extension in subfolder"; then ((PASS++)); else ((FAIL++)); fi
echo ""
echo "Testing AsyncURIMatcher factory methods:"
# Factory exact match (should NOT match subpaths)
if test_route "/factory/exact" "200" "Factory exact match"; then ((PASS++)); else ((FAIL++)); fi
# Factory prefix match
if test_route "/factory/prefix" "200" "Factory prefix base"; then ((PASS++)); else ((FAIL++)); fi
if test_route "/factory/prefix-test" "200" "Factory prefix extended"; then ((PASS++)); else ((FAIL++)); fi
if test_route "/factory/prefix/sub" "200" "Factory prefix subpath"; then ((PASS++)); else ((FAIL++)); fi
# Factory directory match (should NOT match the directory itself)
if test_route "/factory/dir/users" "200" "Factory directory match"; then ((PASS++)); else ((FAIL++)); fi
if test_route "/factory/dir/sub/path" "200" "Factory directory deep"; then ((PASS++)); else ((FAIL++)); fi
# Factory extension match
if test_route "/factory/files/doc.txt" "200" "Factory extension match"; then ((PASS++)); else ((FAIL++)); fi
if test_route "/factory/files/sub/readme.txt" "200" "Factory extension deep"; then ((PASS++)); else ((FAIL++)); fi
echo ""
echo "Testing case insensitive matching:"
# Case insensitive exact
if test_route "/case/exact" "200" "Case exact lowercase"; then ((PASS++)); else ((FAIL++)); fi
if test_route "/CASE/EXACT" "200" "Case exact uppercase"; then ((PASS++)); else ((FAIL++)); fi
if test_route "/Case/Exact" "200" "Case exact mixed"; then ((PASS++)); else ((FAIL++)); fi
# Case insensitive prefix
if test_route "/case/prefix" "200" "Case prefix lowercase"; then ((PASS++)); else ((FAIL++)); fi
if test_route "/CASE/PREFIX-test" "200" "Case prefix uppercase"; then ((PASS++)); else ((FAIL++)); fi
if test_route "/Case/Prefix/sub" "200" "Case prefix mixed"; then ((PASS++)); else ((FAIL++)); fi
# Case insensitive directory
if test_route "/case/dir/users" "200" "Case dir lowercase"; then ((PASS++)); else ((FAIL++)); fi
if test_route "/CASE/DIR/admin" "200" "Case dir uppercase"; then ((PASS++)); else ((FAIL++)); fi
if test_route "/Case/Dir/settings" "200" "Case dir mixed"; then ((PASS++)); else ((FAIL++)); fi
# Case insensitive extension
if test_route "/case/files/doc.pdf" "200" "Case ext lowercase"; then ((PASS++)); else ((FAIL++)); fi
if test_route "/CASE/FILES/DOC.PDF" "200" "Case ext uppercase"; then ((PASS++)); else ((FAIL++)); fi
if test_route "/Case/Files/Doc.Pdf" "200" "Case ext mixed"; then ((PASS++)); else ((FAIL++)); fi
echo ""
echo "Testing special matchers:"
# Test POST to catch-all (all() matcher)
echo -n "Testing POST /any/path (all matcher) ... "
response=$(curl -s -X POST -w "HTTPSTATUS:%{http_code}" "$BASE_URL/any/path" 2>/dev/null)
status_code=$(echo "$response" | grep -o "HTTPSTATUS:[0-9]*" | cut -d: -f2)
if [ "$status_code" = "200" ]; then
echo "✅ PASS ($status_code)"
((PASS++))
else
echo "❌ FAIL (expected 200, got $status_code)"
((FAIL++))
fi
# Check if regex is enabled by testing the server
echo ""
echo "Checking for regex support..."
regex_test=$(curl -s "$BASE_URL/user/123" 2>/dev/null)
if curl -s -w "%{http_code}" "$BASE_URL/user/123" 2>/dev/null | grep -q "200"; then
echo "Regex support detected - testing traditional regex routes:"
if test_route "/user/123" "200" "Traditional regex user ID"; then ((PASS++)); else ((FAIL++)); fi
if test_route "/user/456" "200" "Traditional regex user ID 2"; then ((PASS++)); else ((FAIL++)); fi
if test_route "/blog/2023/10/15" "200" "Traditional regex blog date"; then ((PASS++)); else ((FAIL++)); fi
if test_route "/blog/2024/12/25" "200" "Traditional regex blog date 2"; then ((PASS++)); else ((FAIL++)); fi
echo "Testing AsyncURIMatcher regex factory methods:"
if test_route "/factory/user/123" "200" "Factory regex user ID"; then ((PASS++)); else ((FAIL++)); fi
if test_route "/factory/user/789" "200" "Factory regex user ID 2"; then ((PASS++)); else ((FAIL++)); fi
if test_route "/factory/blog/2023/10/15" "200" "Factory regex blog date"; then ((PASS++)); else ((FAIL++)); fi
if test_route "/factory/blog/2024/12/31" "200" "Factory regex blog date 2"; then ((PASS++)); else ((FAIL++)); fi
# Case insensitive regex
if test_route "/factory/search/hello" "200" "Factory regex search lowercase"; then ((PASS++)); else ((FAIL++)); fi
if test_route "/FACTORY/SEARCH/WORLD" "200" "Factory regex search uppercase"; then ((PASS++)); else ((FAIL++)); fi
if test_route "/Factory/Search/Test" "200" "Factory regex search mixed"; then ((PASS++)); else ((FAIL++)); fi
# Test regex validation
if test_route "/user/abc" "404" "Invalid regex (letters instead of numbers)"; then ((PASS++)); else ((FAIL++)); fi
if test_route "/blog/23/10/15" "404" "Invalid regex (2-digit year)"; then ((PASS++)); else ((FAIL++)); fi
if test_route "/factory/user/abc" "404" "Factory regex invalid (letters)"; then ((PASS++)); else ((FAIL++)); fi
else
echo "Regex support not detected (compile with ASYNCWEBSERVER_REGEX to enable)"
fi
echo ""
echo "Testing routes that should fail (404 Not Found):"
if test_route "/nonexistent" "404" "Non-existent route"; then ((PASS++)); else ((FAIL++)); fi
# Test factory exact vs traditional behavior difference
if test_route "/factory/exact/sub" "404" "Factory exact should NOT match subpaths"; then ((PASS++)); else ((FAIL++)); fi
# Test factory directory requires trailing slash
if test_route "/factory/dir" "404" "Factory directory should NOT match without trailing slash"; then ((PASS++)); else ((FAIL++)); fi
# Test extension mismatch
if test_route "/factory/files/doc.pdf" "404" "Factory extension mismatch (.pdf vs .txt)"; then ((PASS++)); else ((FAIL++)); fi
# Test case sensitive when flag not used
if test_route "/exact" "200" "Traditional exact lowercase"; then ((PASS++)); else ((FAIL++)); fi
if test_route "/EXACT" "404" "Traditional exact should be case sensitive"; then ((PASS++)); else ((FAIL++)); fi
echo ""
echo "=================================="
echo "Test Results:"
echo "✅ Passed: $PASS"
echo "❌ Failed: $FAIL"
echo "Total: $((PASS + FAIL))"
if [ $FAIL -eq 0 ]; then
echo ""
echo "🎉 All tests passed! URI matching is working correctly."
exit 0
else
echo ""
echo "❌ Some tests failed. Check the server and routes."
exit 1
fi

View File

@@ -1,175 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2026 Hristo Gochkov, Mathieu Carbou, Emil Muratov, Will Miles
//
// Demo text, binary and file upload
//
#include <Arduino.h>
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
#include <StreamString.h>
#include <LittleFS.h>
static AsyncWebServer server(80);
void setup() {
Serial.begin(115200);
if (!LittleFS.begin()) {
LittleFS.format();
LittleFS.begin();
}
#if ASYNCWEBSERVER_WIFI_SUPPORTED
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
// 1. Generate a Lorem_ipsum.txt file of about 20KB of text
//
// 3. Run: curl -v -F "data=@Lorem_ipsum.txt" http://192.168.4.1/upload/text
//
server.on(
"/upload/text", HTTP_POST,
[](AsyncWebServerRequest *request) {
if (!request->_tempObject) {
return request->send(400, "text/plain", "Nothing uploaded");
}
StreamString *buffer = reinterpret_cast<StreamString *>(request->_tempObject);
Serial.printf("Text uploaded:\n%s\n", buffer->c_str());
delete buffer;
request->_tempObject = nullptr;
request->send(200, "text/plain", "OK");
},
[](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
Serial.printf("Upload[%s]: start=%u, len=%u, final=%d\n", filename.c_str(), index, len, final);
if (!index) {
// first pass
StreamString *buffer = new StreamString();
size_t size = std::max(4094l, request->header("Content-Length").toInt());
Serial.printf("Allocating string buffer of %u bytes\n", size);
if (!buffer->reserve(size)) {
delete buffer;
request->abort();
return;
}
request->_tempObject = buffer;
}
if (len) {
reinterpret_cast<StreamString *>(request->_tempObject)->write(data, len);
}
}
);
// 1. Generate a Lorem_ipsum.txt file of about 20KB of text
//
// 3. Run: curl -v -F "data=@Lorem_ipsum.txt" http://192.168.4.1/upload/file
//
server.on(
"/upload/file", HTTP_POST,
[](AsyncWebServerRequest *request) {
if (request->getResponse()) {
// 400 File not available for writing
return;
}
if (!LittleFS.exists("/my_file.txt")) {
return request->send(400, "text/plain", "Nothing uploaded");
}
// sends back the uploaded file
request->send(LittleFS, "/my_file.txt", "text/plain");
},
[](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
Serial.printf("Upload[%s]: start=%u, len=%u, final=%d\n", filename.c_str(), index, len, final);
if (!index) {
request->_tempFile = LittleFS.open("/my_file.txt", "w");
if (!request->_tempFile) {
request->send(400, "text/plain", "File not available for writing");
return;
}
}
if (len) {
request->_tempFile.write(data, len);
}
if (final) {
request->_tempFile.close();
}
}
);
//
// Upload a binary file: curl -v -F "data=@file.mp3" http://192.168.4.1/upload/binary
//
server.on(
"/upload/binary", HTTP_POST,
[](AsyncWebServerRequest *request) {
// response already set ?
if (request->getResponse()) {
// 400 No Content-Length
return;
}
// nothing uploaded ?
if (!request->_tempObject) {
return request->send(400, "text/plain", "Nothing uploaded");
}
uint8_t *buffer = reinterpret_cast<uint8_t *>(request->_tempObject);
// process the buffer
delete buffer;
request->_tempObject = nullptr;
request->send(200, "text/plain", "OK");
},
[](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
Serial.printf("Upload[%s]: start=%u, len=%u, final=%d\n", filename.c_str(), index, len, final);
// first pass ?
if (!index) {
// Note: using content type to determine size is not reliable!
size_t size = request->header("Content-Length").toInt();
if (!size) {
request->send(400, "text/plain", "No Content-Length");
} else {
Serial.printf("Allocating buffer of %u bytes\n", size);
uint8_t *buffer = new (std::nothrow) uint8_t[size];
if (!buffer) {
// not enough memory
request->abort();
return;
} else {
request->_tempObject = buffer;
}
}
}
if (len) {
memcpy(reinterpret_cast<uint8_t *>(request->_tempObject) + index, data, len);
}
}
);
server.begin();
}
// not needed
void loop() {
delay(100);
}

View File

@@ -1,158 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2026 Hristo Gochkov, Mathieu Carbou, Emil Muratov, Will Miles
//
// Demo to upload a firmware and filesystem image via multipart form data
//
#include <Arduino.h>
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
#include <StreamString.h>
#include <LittleFS.h>
// ESP32 example ONLY
#ifdef ESP32
#include <Update.h>
#endif
static AsyncWebServer server(80);
void setup() {
Serial.begin(115200);
if (!LittleFS.begin()) {
LittleFS.format();
LittleFS.begin();
}
#if ASYNCWEBSERVER_WIFI_SUPPORTED
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
// ESP32 example ONLY
#ifdef ESP32
// Shows how to get the fw and fs (names) and filenames from a multipart upload,
// and also how to handle multiple file uploads in a single request.
//
// This example also shows how to pass and handle different parameters having the same name in query string, post form and content-disposition.
//
// Execute in the terminal, in order:
//
// 1. Build firmware: pio run -e arduino-3
// 2. Build FS image: pio run -e arduino-3 -t buildfs
// 3. Flash both at the same time: curl -v -F "name=Bob" -F "fw=@.pio/build/arduino-3/firmware.bin" -F "fs=@.pio/build/arduino-3/littlefs.bin" http://192.168.4.1/flash?name=Bill
//
server.on(
"/flash", HTTP_POST,
[](AsyncWebServerRequest *request) {
if (request->getResponse()) {
// response already created
return;
}
// list all parameters
Serial.println("Request parameters:");
const size_t params = request->params();
for (size_t i = 0; i < params; i++) {
const AsyncWebParameter *p = request->getParam(i);
Serial.printf("Param[%u]: %s=%s, isPost=%d, isFile=%d, size=%u\n", i, p->name().c_str(), p->value().c_str(), p->isPost(), p->isFile(), p->size());
}
Serial.println("Flash / Filesystem upload completed");
request->send(200, "text/plain", "Upload complete");
},
[](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
Serial.printf("Upload[%s]: index=%u, len=%u, final=%d\n", filename.c_str(), index, len, final);
if (request->getResponse() != nullptr) {
// upload aborted
return;
}
// start a new content-disposition upload
if (!index) {
// list all parameters
const size_t params = request->params();
for (size_t i = 0; i < params; i++) {
const AsyncWebParameter *p = request->getParam(i);
Serial.printf("Param[%u]: %s=%s, isPost=%d, isFile=%d, size=%u\n", i, p->name().c_str(), p->value().c_str(), p->isPost(), p->isFile(), p->size());
}
// get the content-disposition parameter
const AsyncWebParameter *p = request->getParam(asyncsrv::T_name, true, true);
if (p == nullptr) {
request->send(400, "text/plain", "Missing content-disposition 'name' parameter");
return;
}
// determine upload type based on the parameter name
if (p->value() == "fs") {
Serial.printf("Filesystem image upload for file: %s\n", filename.c_str());
if (!Update.begin(UPDATE_SIZE_UNKNOWN, U_SPIFFS)) {
Update.printError(Serial);
request->send(400, "text/plain", "Update begin failed");
return;
}
} else if (p->value() == "fw") {
Serial.printf("Firmware image upload for file: %s\n", filename.c_str());
if (!Update.begin(UPDATE_SIZE_UNKNOWN, U_FLASH)) {
Update.printError(Serial);
request->send(400, "text/plain", "Update begin failed");
return;
}
} else {
Serial.printf("Unknown upload type for file: %s\n", filename.c_str());
request->send(400, "text/plain", "Unknown upload type");
return;
}
}
// some bytes to write ?
if (len) {
if (Update.write(data, len) != len) {
Update.printError(Serial);
Update.end();
request->send(400, "text/plain", "Update write failed");
return;
}
}
// finish the content-disposition upload
if (final) {
if (!Update.end(true)) {
Update.printError(Serial);
request->send(400, "text/plain", "Update end failed");
return;
}
// success response is created in the final request handler when all uploads are completed
Serial.printf("Upload success of file %s\n", filename.c_str());
}
}
);
#endif
server.begin();
}
// not needed
void loop() {
delay(100);
}

View File

@@ -1,65 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2026 Hristo Gochkov, Mathieu Carbou, Emil Muratov, Mitch Bradley
//
// - Test for additional WebDAV request methods
//
#include <Arduino.h>
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
using namespace asyncsrv;
// Tests:
//
// Send requests with various methods
// curl -s -X PROPFIND http://192.168.4.1/
// curl -s -X LOCK http://192.168.4.1/
// curl -s -X UNLOCK http://192.168.4.1/
// curl -s -X PROPPATCH http://192.168.4.1/
// curl -s -X MKCOL http://192.168.4.1/
// curl -s -X MOVE http://192.168.4.1/
// curl -s -X COPY http://192.168.4.1/
//
// In all cases, the request will be accepted with text/plain response 200 like
// "Got method PROPFIND on URL /"
static AsyncWebServer server(80);
void setup() {
Serial.begin(115200);
#if ASYNCWEBSERVER_WIFI_SUPPORTED
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
server.onNotFound([](AsyncWebServerRequest *request) {
String resp("Got method ");
resp += request->methodToString();
resp += " on URL ";
resp += request->url();
resp += "\r\n";
Serial.print(resp);
request->send(200, "text/plain", resp.c_str());
});
server.begin();
}
void loop() {
delay(100);
}

View File

@@ -1,215 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2026 Hristo Gochkov, Mathieu Carbou, Emil Muratov, Will Miles
//
// WebSocket example
//
#include <Arduino.h>
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
static const char *htmlContent PROGMEM = R"(
<!DOCTYPE html>
<html>
<head>
<title>WebSocket</title>
</head>
<body>
<h1>WebSocket Example</h1>
<p>Open your browser console!</p>
<input type="text" id="message" placeholder="Type a message">
<button onclick='sendMessage()'>Send</button>
<script>
var ws = new WebSocket('ws://192.168.4.1/ws');
ws.onopen = function() {
console.log("WebSocket connected");
};
ws.onmessage = function(event) {
console.log("WebSocket message: " + event.data);
};
ws.onclose = function() {
console.log("WebSocket closed");
};
ws.onerror = function(error) {
console.log("WebSocket error: " + error);
};
function sendMessage() {
var message = document.getElementById("message").value;
ws.send(message);
console.log("WebSocket sent: " + message);
}
setInterval(function() {
if (ws.readyState === WebSocket.OPEN) {
ws.send("msg from browser");
}
}, 1000);
</script>
</body>
</html>
)";
static const size_t htmlContentLength = strlen_P(htmlContent);
static AsyncWebServer server(80);
static AsyncWebSocket ws("/ws");
void setup() {
Serial.begin(115200);
#if ASYNCWEBSERVER_WIFI_SUPPORTED
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
// serves root html page
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "text/html", (const uint8_t *)htmlContent, htmlContentLength);
});
//
// Run in terminal 1: websocat ws://192.168.4.1/ws => should stream data
// Run in terminal 2: websocat ws://192.168.4.1/ws => should stream data
// Run in terminal 3: websocat ws://192.168.4.1/ws => should fail:
//
// To send a message to the WebSocket server (\n at the end):
// > echo "Hello!" | websocat ws://192.168.4.1/ws
//
// Generates 2001 characters (\n at the end) to cause a fragmentation (over TCP MSS):
// > openssl rand -hex 1000 | websocat ws://192.168.4.1/ws
//
ws.onEvent([](AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) {
(void)len;
if (type == WS_EVT_CONNECT) {
ws.textAll("new client connected");
Serial.println("ws connect");
client->setCloseClientOnQueueFull(false);
client->ping();
} else if (type == WS_EVT_DISCONNECT) {
ws.textAll("client disconnected");
Serial.println("ws disconnect");
} else if (type == WS_EVT_ERROR) {
Serial.println("ws error");
} else if (type == WS_EVT_PONG) {
Serial.println("ws pong");
} else if (type == WS_EVT_DATA) {
AwsFrameInfo *info = (AwsFrameInfo *)arg;
Serial.printf(
"index: %" PRIu64 ", len: %" PRIu64 ", final: %" PRIu8 ", opcode: %" PRIu8 ", framelen: %d\n", info->index, info->len, info->final,
info->message_opcode, len
);
// complete frame
if (info->final && info->index == 0 && info->len == len) {
if (info->message_opcode == WS_TEXT) {
Serial.printf("ws text: %s\n", (char *)data);
client->ping();
}
} else {
// incomplete frame
if (info->index == 0) {
if (info->num == 0) {
Serial.printf(
"ws[%s][%" PRIu32 "] [%" PRIu32 "] MSG START %s\n", server->url(), client->id(), info->num, (info->message_opcode == WS_TEXT) ? "text" : "binary"
);
}
Serial.printf("ws[%s][%" PRIu32 "] [%" PRIu32 "] FRAME START len=%" PRIu64 "\n", server->url(), client->id(), info->num, info->len);
}
Serial.printf(
"ws[%s][%" PRIu32 "] [%" PRIu32 "] FRAME %s, index=%" PRIu64 ", len=%" PRIu32 "]: ", server->url(), client->id(), info->num,
(info->message_opcode == WS_TEXT) ? "text" : "binary", info->index, (uint32_t)len
);
if (info->message_opcode == WS_TEXT) {
Serial.printf("%s\n", (char *)data);
} else {
for (size_t i = 0; i < len; i++) {
Serial.printf("%02x ", data[i]);
}
Serial.printf("\n");
}
if ((info->index + len) == info->len) {
Serial.printf("ws[%s][%" PRIu32 "] [%" PRIu32 "] FRAME END\n", server->url(), client->id(), info->num);
if (info->final) {
Serial.printf("ws[%s][%" PRIu32 "] [%" PRIu32 "] MSG END\n", server->url(), client->id(), info->num);
}
}
}
}
});
// shows how to prevent a third WS client to connect
server.addHandler(&ws).addMiddleware([](AsyncWebServerRequest *request, ArMiddlewareNext next) {
// ws.count() is the current count of WS clients: this one is trying to upgrade its HTTP connection
if (ws.count() > 1) {
// if we have 2 clients or more, prevent the next one to connect
request->send(503, "text/plain", "Server is busy");
} else {
// process next middleware and at the end the handler
next();
}
});
server.addHandler(&ws);
server.begin();
}
#ifdef ESP32
static const uint32_t deltaWS = 50;
#else
static const uint32_t deltaWS = 200;
#endif
static uint32_t lastWS = 0;
static uint32_t lastHeap = 0;
void loop() {
uint32_t now = millis();
if (now - lastWS >= deltaWS) {
ws.printfAll("kp:%.4f", (10.0 / 3.0));
lastWS = millis();
}
if (now - lastHeap >= 5000) {
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
String random;
random.reserve(8192);
for (size_t i = 0; i < 8192; i++) {
random += "x";
}
ws.textAll(random);
// ping twice (2 control frames)
ws.pingAll();
ws.pingAll();
#ifdef ESP32
Serial.printf("Free heap: %" PRIu32 "\n", ESP.getFreeHeap());
#endif
lastHeap = now;
}
}

View File

@@ -1,125 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2026 Hristo Gochkov, Mathieu Carbou, Emil Muratov, Will Miles
//
// WebSocket example using the easy to use AsyncWebSocketMessageHandler handler that only supports unfragmented messages
//
#include <Arduino.h>
#if defined(ESP32) || defined(LIBRETINY)
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
static AsyncWebServer server(80);
// create an easy-to-use handler
static AsyncWebSocketMessageHandler wsHandler;
// add it to the websocket server
static AsyncWebSocket ws("/ws", wsHandler.eventHandler());
// alternatively you can do as usual:
//
// static AsyncWebSocket ws("/ws");
// ws.onEvent(wsHandler.eventHandler());
static const char *htmlContent PROGMEM = R"(
<!DOCTYPE html>
<html>
<head>
<title>WebSocket</title>
</head>
<body>
<h1>WebSocket Example</h1>
<p>Open your browser console!</p>
<input type="text" id="message" placeholder="Type a message">
<button onclick='sendMessage()'>Send</button>
<script>
var ws = new WebSocket('ws://192.168.4.1/ws');
ws.onopen = function() {
console.log("WebSocket connected");
};
ws.onmessage = function(event) {
console.log("WebSocket message: " + event.data);
};
ws.onclose = function() {
console.log("WebSocket closed");
};
ws.onerror = function(error) {
console.log("WebSocket error: " + error);
};
function sendMessage() {
var message = document.getElementById("message").value;
ws.send(message);
console.log("WebSocket sent: " + message);
}
</script>
</body>
</html>
)";
static const size_t htmlContentLength = strlen_P(htmlContent);
void setup() {
Serial.begin(115200);
#if ASYNCWEBSERVER_WIFI_SUPPORTED
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
// serves root html page
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "text/html", (const uint8_t *)htmlContent, htmlContentLength);
});
wsHandler.onConnect([](AsyncWebSocket *server, AsyncWebSocketClient *client) {
Serial.printf("Client %" PRIu32 " connected\n", client->id());
server->textAll("New client: " + String(client->id()));
});
wsHandler.onDisconnect([](AsyncWebSocket *server, uint32_t clientId) {
Serial.printf("Client %" PRIu32 " disconnected\n", clientId);
server->textAll("Client " + String(clientId) + " disconnected");
});
wsHandler.onError([](AsyncWebSocket *server, AsyncWebSocketClient *client, uint16_t errorCode, const char *reason, size_t len) {
Serial.printf("Client %" PRIu32 " error: %" PRIu16 ": %s\n", client->id(), errorCode, reason);
});
wsHandler.onMessage([](AsyncWebSocket *server, AsyncWebSocketClient *client, const uint8_t *data, size_t len) {
Serial.printf("Client %" PRIu32 " data: %s\n", client->id(), (const char *)data);
server->textAll(data, len);
});
wsHandler.onFragment([](AsyncWebSocket *server, AsyncWebSocketClient *client, const AwsFrameInfo *frameInfo, const uint8_t *data, size_t len) {
Serial.printf("Client %" PRIu32 " fragment %" PRIu32 ": %s\n", client->id(), frameInfo->num, (const char *)data);
});
server.addHandler(&ws);
server.begin();
}
static uint32_t lastWS = 0;
static uint32_t deltaWS = 2000;
void loop() {
uint32_t now = millis();
if (now - lastWS >= deltaWS) {
ws.cleanupClients();
ws.printfAll("now: %" PRIu32 "\n", now);
lastWS = millis();
#ifdef ESP32
Serial.printf("Free heap: %" PRIu32 "\n", ESP.getFreeHeap());
#endif
}
}

View File

@@ -0,0 +1 @@
-DASYNCWEBSERVER_REGEX=1

View File

@@ -0,0 +1,77 @@
//
// A simple server implementation with regex routes:
// * serve static messages
// * read GET and POST parameters
// * handle missing pages / 404s
//
// Add buildflag ASYNCWEBSERVER_REGEX to enable the regex support
// For platformio: platformio.ini:
// build_flags =
// -DASYNCWEBSERVER_REGEX
// For arduino IDE: create/update platform.local.txt
// Windows: C:\Users\(username)\AppData\Local\Arduino15\packages\espxxxx\hardware\espxxxx\{version}\platform.local.txt
// Linux: ~/.arduino15/packages/espxxxx/hardware/espxxxx/{version}/platform.local.txt
//
// compiler.cpp.extra_flags=-DASYNCWEBSERVER_REGEX=1
#include <Arduino.h>
#ifdef ESP32
#include <WiFi.h>
#include <AsyncTCP.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#endif
#include <ESPAsyncWebServer.h>
AsyncWebServer server(80);
const char* ssid = "YOUR_SSID";
const char* password = "YOUR_PASSWORD";
const char* PARAM_MESSAGE = "message";
void notFound(AsyncWebServerRequest *request) {
request->send(404, "text/plain", "Not found");
}
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
if (WiFi.waitForConnectResult() != WL_CONNECTED) {
Serial.printf("WiFi Failed!\n");
return;
}
Serial.print("IP Address: ");
Serial.println(WiFi.localIP());
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(200, "text/plain", "Hello, world");
});
// Send a GET request to <IP>/sensor/<number>
server.on("^\\/sensor\\/([0-9]+)$", HTTP_GET, [] (AsyncWebServerRequest *request) {
String sensorNumber = request->pathArg(0);
request->send(200, "text/plain", "Hello, sensor: " + sensorNumber);
});
// Send a GET request to <IP>/sensor/<number>/action/<action>
server.on("^\\/sensor\\/([0-9]+)\\/action\\/([a-zA-Z0-9]+)$", HTTP_GET, [] (AsyncWebServerRequest *request) {
String sensorNumber = request->pathArg(0);
String action = request->pathArg(1);
request->send(200, "text/plain", "Hello, sensor: " + sensorNumber + ", with action: " + action);
});
server.onNotFound(notFound);
server.begin();
}
void loop() {
}

View File

@@ -0,0 +1,74 @@
//
// A simple server implementation showing how to:
// * serve static messages
// * read GET and POST parameters
// * handle missing pages / 404s
//
#include <Arduino.h>
#ifdef ESP32
#include <WiFi.h>
#include <AsyncTCP.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#endif
#include <ESPAsyncWebServer.h>
AsyncWebServer server(80);
const char* ssid = "YOUR_SSID";
const char* password = "YOUR_PASSWORD";
const char* PARAM_MESSAGE = "message";
void notFound(AsyncWebServerRequest *request) {
request->send(404, "text/plain", "Not found");
}
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
if (WiFi.waitForConnectResult() != WL_CONNECTED) {
Serial.printf("WiFi Failed!\n");
return;
}
Serial.print("IP Address: ");
Serial.println(WiFi.localIP());
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(200, "text/plain", "Hello, world");
});
// Send a GET request to <IP>/get?message=<message>
server.on("/get", HTTP_GET, [] (AsyncWebServerRequest *request) {
String message;
if (request->hasParam(PARAM_MESSAGE)) {
message = request->getParam(PARAM_MESSAGE)->value();
} else {
message = "No message sent";
}
request->send(200, "text/plain", "Hello, GET: " + message);
});
// Send a POST request to <IP>/post with a form field message set to <message>
server.on("/post", HTTP_POST, [](AsyncWebServerRequest *request){
String message;
if (request->hasParam(PARAM_MESSAGE, true)) {
message = request->getParam(PARAM_MESSAGE, true)->value();
} else {
message = "No message sent";
}
request->send(200, "text/plain", "Hello, POST: " + message);
});
server.onNotFound(notFound);
server.begin();
}
void loop() {
}

View File

@@ -1,40 +0,0 @@
description: "Async Web Server for ESP32 Arduino"
url: "https://github.com/ESP32Async/ESPAsyncWebServer"
license: "LGPL-3.0-or-later"
tags:
- arduino
files:
exclude:
- "idf_component_examples/"
- "idf_component_examples/**/*"
- "docs/"
- "docs/*"
- "examples/"
- "examples/**/*"
- ".gitignore"
- ".clang-format"
- ".gitpod.Dockerfile"
- ".gitpod.yml"
- ".codespellrc"
- ".editorconfig"
- ".pre-commit-config.yaml"
- "CODE_OF_CONDUCT.md"
- "library.json"
- "library.properties"
- "partitions-4MB.csv"
- "platformio.ini"
- "pre-commit.requirements.txt"
dependencies:
espressif/arduino-esp32:
version: "^3.1.1"
require: public
esp32async/asynctcp:
version: "^3.4.10"
require: public
bblanchon/arduinojson:
version: "^7.4.2"
require: public
examples:
- path: ./idf_component_examples/catchall
- path: ./idf_component_examples/serversentevents
- path: ./idf_component_examples/websocket

View File

@@ -1,8 +0,0 @@
# For more information about build system see
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
# The following five lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(main)

View File

@@ -1 +0,0 @@
### Basic example to show how to catch all requests and send a 404 Not Found response

View File

@@ -1,2 +0,0 @@
idf_component_register(SRCS "main.cpp"
INCLUDE_DIRS ".")

Some files were not shown because too many files have changed in this diff Show More