Youen
7 months ago
57 changed files with 314 additions and 9871 deletions
@ -1,36 +0,0 @@ |
|||||||
#!/bin/bash |
|
||||||
|
|
||||||
export ARDUINO_ESP32_PATH="$ARDUINO_USR_PATH/hardware/espressif/esp32" |
|
||||||
if [ ! -d "$ARDUINO_ESP32_PATH" ]; then |
|
||||||
echo "Installing ESP32 Arduino Core ..." |
|
||||||
script_init_path="$PWD" |
|
||||||
mkdir -p "$ARDUINO_USR_PATH/hardware/espressif" |
|
||||||
cd "$ARDUINO_USR_PATH/hardware/espressif" |
|
||||||
|
|
||||||
echo "Installing Python Serial ..." |
|
||||||
pip install pyserial > /dev/null |
|
||||||
|
|
||||||
if [ "$OS_IS_WINDOWS" == "1" ]; then |
|
||||||
echo "Installing Python Requests ..." |
|
||||||
pip install requests > /dev/null |
|
||||||
fi |
|
||||||
|
|
||||||
if [ "$GITHUB_REPOSITORY" == "espressif/arduino-esp32" ]; then |
|
||||||
echo "Linking Core..." |
|
||||||
ln -s $GITHUB_WORKSPACE esp32 |
|
||||||
else |
|
||||||
echo "Cloning Core Repository..." |
|
||||||
git clone https://github.com/espressif/arduino-esp32.git esp32 > /dev/null 2>&1 |
|
||||||
fi |
|
||||||
|
|
||||||
echo "Updating Submodules ..." |
|
||||||
cd esp32 |
|
||||||
git submodule update --init --recursive > /dev/null 2>&1 |
|
||||||
|
|
||||||
echo "Installing Platform Tools ..." |
|
||||||
cd tools && python get.py |
|
||||||
cd $script_init_path |
|
||||||
|
|
||||||
echo "ESP32 Arduino has been installed in '$ARDUINO_ESP32_PATH'" |
|
||||||
echo "" |
|
||||||
fi |
|
@ -1,29 +0,0 @@ |
|||||||
#!/bin/bash |
|
||||||
|
|
||||||
echo "Installing ESP8266 Arduino Core ..." |
|
||||||
script_init_path="$PWD" |
|
||||||
mkdir -p "$ARDUINO_USR_PATH/hardware/esp8266com" |
|
||||||
cd "$ARDUINO_USR_PATH/hardware/esp8266com" |
|
||||||
|
|
||||||
echo "Installing Python Serial ..." |
|
||||||
pip install pyserial > /dev/null |
|
||||||
|
|
||||||
if [ "$OS_IS_WINDOWS" == "1" ]; then |
|
||||||
echo "Installing Python Requests ..." |
|
||||||
pip install requests > /dev/null |
|
||||||
fi |
|
||||||
|
|
||||||
echo "Cloning Core Repository ..." |
|
||||||
git clone https://github.com/esp8266/Arduino.git esp8266 > /dev/null 2>&1 |
|
||||||
|
|
||||||
echo "Updating submodules ..." |
|
||||||
cd esp8266 |
|
||||||
git submodule update --init --recursive > /dev/null 2>&1 |
|
||||||
|
|
||||||
echo "Installing Platform Tools ..." |
|
||||||
cd tools |
|
||||||
python get.py > /dev/null |
|
||||||
cd $script_init_path |
|
||||||
|
|
||||||
echo "ESP8266 Arduino has been installed in '$ARDUINO_USR_PATH/hardware/esp8266com'" |
|
||||||
echo "" |
|
@ -1,228 +0,0 @@ |
|||||||
#!/bin/bash |
|
||||||
|
|
||||||
#OSTYPE: 'linux-gnu', ARCH: 'x86_64' => linux64 |
|
||||||
#OSTYPE: 'msys', ARCH: 'x86_64' => win32 |
|
||||||
#OSTYPE: 'darwin18', ARCH: 'i386' => macos |
|
||||||
|
|
||||||
OSBITS=`arch` |
|
||||||
if [[ "$OSTYPE" == "linux"* ]]; then |
|
||||||
export OS_IS_LINUX="1" |
|
||||||
ARCHIVE_FORMAT="tar.xz" |
|
||||||
if [[ "$OSBITS" == "i686" ]]; then |
|
||||||
OS_NAME="linux32" |
|
||||||
elif [[ "$OSBITS" == "x86_64" ]]; then |
|
||||||
OS_NAME="linux64" |
|
||||||
elif [[ "$OSBITS" == "armv7l" || "$OSBITS" == "aarch64" ]]; then |
|
||||||
OS_NAME="linuxarm" |
|
||||||
else |
|
||||||
OS_NAME="$OSTYPE-$OSBITS" |
|
||||||
echo "Unknown OS '$OS_NAME'" |
|
||||||
exit 1 |
|
||||||
fi |
|
||||||
elif [[ "$OSTYPE" == "darwin"* ]]; then |
|
||||||
export OS_IS_MACOS="1" |
|
||||||
ARCHIVE_FORMAT="zip" |
|
||||||
OS_NAME="macosx" |
|
||||||
elif [[ "$OSTYPE" == "cygwin" ]] || [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "win32" ]]; then |
|
||||||
export OS_IS_WINDOWS="1" |
|
||||||
ARCHIVE_FORMAT="zip" |
|
||||||
OS_NAME="windows" |
|
||||||
else |
|
||||||
OS_NAME="$OSTYPE-$OSBITS" |
|
||||||
echo "Unknown OS '$OS_NAME'" |
|
||||||
exit 1 |
|
||||||
fi |
|
||||||
export OS_NAME |
|
||||||
|
|
||||||
ARDUINO_BUILD_DIR="$HOME/.arduino/build.tmp" |
|
||||||
ARDUINO_CACHE_DIR="$HOME/.arduino/cache.tmp" |
|
||||||
|
|
||||||
if [ "$OS_IS_MACOS" == "1" ]; then |
|
||||||
export ARDUINO_IDE_PATH="/Applications/Arduino.app/Contents/Java" |
|
||||||
export ARDUINO_USR_PATH="$HOME/Documents/Arduino" |
|
||||||
elif [ "$OS_IS_WINDOWS" == "1" ]; then |
|
||||||
export ARDUINO_IDE_PATH="$HOME/arduino_ide" |
|
||||||
export ARDUINO_USR_PATH="$HOME/Documents/Arduino" |
|
||||||
else |
|
||||||
export ARDUINO_IDE_PATH="$HOME/arduino_ide" |
|
||||||
export ARDUINO_USR_PATH="$HOME/Arduino" |
|
||||||
fi |
|
||||||
|
|
||||||
if [ ! -d "$ARDUINO_IDE_PATH" ]; then |
|
||||||
echo "Installing Arduino IDE on $OS_NAME ..." |
|
||||||
echo "Downloading 'arduino-nightly-$OS_NAME.$ARCHIVE_FORMAT' to 'arduino.$ARCHIVE_FORMAT' ..." |
|
||||||
if [ "$OS_IS_LINUX" == "1" ]; then |
|
||||||
wget -O "arduino.$ARCHIVE_FORMAT" "https://www.arduino.cc/download.php?f=/arduino-nightly-$OS_NAME.$ARCHIVE_FORMAT" > /dev/null 2>&1 |
|
||||||
echo "Extracting 'arduino.$ARCHIVE_FORMAT' ..." |
|
||||||
tar xf "arduino.$ARCHIVE_FORMAT" > /dev/null |
|
||||||
mv arduino-nightly "$ARDUINO_IDE_PATH" |
|
||||||
else |
|
||||||
curl -o "arduino.$ARCHIVE_FORMAT" -L "https://www.arduino.cc/download.php?f=/arduino-nightly-$OS_NAME.$ARCHIVE_FORMAT" > /dev/null 2>&1 |
|
||||||
echo "Extracting 'arduino.$ARCHIVE_FORMAT' ..." |
|
||||||
unzip "arduino.$ARCHIVE_FORMAT" > /dev/null |
|
||||||
if [ "$OS_IS_MACOS" == "1" ]; then |
|
||||||
mv "Arduino.app" "/Applications/Arduino.app" |
|
||||||
else |
|
||||||
mv arduino-nightly "$ARDUINO_IDE_PATH" |
|
||||||
fi |
|
||||||
fi |
|
||||||
rm -rf "arduino.$ARCHIVE_FORMAT" |
|
||||||
|
|
||||||
mkdir -p "$ARDUINO_USR_PATH/libraries" |
|
||||||
mkdir -p "$ARDUINO_USR_PATH/hardware" |
|
||||||
|
|
||||||
echo "Arduino IDE Installed in '$ARDUINO_IDE_PATH'" |
|
||||||
echo "" |
|
||||||
fi |
|
||||||
|
|
||||||
function build_sketch(){ # build_sketch <fqbn> <path-to-ino> <build-flags> [extra-options] |
|
||||||
if [ "$#" -lt 2 ]; then |
|
||||||
echo "ERROR: Illegal number of parameters" |
|
||||||
echo "USAGE: build_sketch <fqbn> <path-to-ino> <build-flags> [extra-options]" |
|
||||||
return 1 |
|
||||||
fi |
|
||||||
|
|
||||||
local fqbn="$1" |
|
||||||
local sketch="$2" |
|
||||||
local build_flags="$3" |
|
||||||
local xtra_opts="$4" |
|
||||||
local win_opts="" |
|
||||||
if [ "$OS_IS_WINDOWS" == "1" ]; then |
|
||||||
local ctags_version=`ls "$ARDUINO_IDE_PATH/tools-builder/ctags/"` |
|
||||||
local preprocessor_version=`ls "$ARDUINO_IDE_PATH/tools-builder/arduino-preprocessor/"` |
|
||||||
win_opts="-prefs=runtime.tools.ctags.path=$ARDUINO_IDE_PATH/tools-builder/ctags/$ctags_version -prefs=runtime.tools.arduino-preprocessor.path=$ARDUINO_IDE_PATH/tools-builder/arduino-preprocessor/$preprocessor_version" |
|
||||||
fi |
|
||||||
|
|
||||||
echo "" |
|
||||||
echo "Compiling '"$(basename "$sketch")"' ..." |
|
||||||
mkdir -p "$ARDUINO_BUILD_DIR" |
|
||||||
mkdir -p "$ARDUINO_CACHE_DIR" |
|
||||||
$ARDUINO_IDE_PATH/arduino-builder -compile -logger=human -core-api-version=10810 \ |
|
||||||
-fqbn=$fqbn \ |
|
||||||
-warnings="all" \ |
|
||||||
-tools "$ARDUINO_IDE_PATH/tools-builder" \ |
|
||||||
-tools "$ARDUINO_IDE_PATH/tools" \ |
|
||||||
-built-in-libraries "$ARDUINO_IDE_PATH/libraries" \ |
|
||||||
-hardware "$ARDUINO_IDE_PATH/hardware" \ |
|
||||||
-hardware "$ARDUINO_USR_PATH/hardware" \ |
|
||||||
-libraries "$ARDUINO_USR_PATH/libraries" \ |
|
||||||
-build-cache "$ARDUINO_CACHE_DIR" \ |
|
||||||
-build-path "$ARDUINO_BUILD_DIR" \ |
|
||||||
-prefs=compiler.cpp.extra_flags="$build_flags" \ |
|
||||||
$win_opts $xtra_opts "$sketch" |
|
||||||
} |
|
||||||
|
|
||||||
function count_sketches() # count_sketches <examples-path> |
|
||||||
{ |
|
||||||
local examples="$1" |
|
||||||
rm -rf sketches.txt |
|
||||||
if [ ! -d "$examples" ]; then |
|
||||||
touch sketches.txt |
|
||||||
return 0 |
|
||||||
fi |
|
||||||
local sketches=$(find $examples -name *.ino) |
|
||||||
local sketchnum=0 |
|
||||||
for sketch in $sketches; do |
|
||||||
local sketchdir=$(dirname $sketch) |
|
||||||
local sketchdirname=$(basename $sketchdir) |
|
||||||
local sketchname=$(basename $sketch) |
|
||||||
if [[ "${sketchdirname}.ino" != "$sketchname" ]]; then |
|
||||||
continue |
|
||||||
fi; |
|
||||||
if [[ -f "$sketchdir/.test.skip" ]]; then |
|
||||||
continue |
|
||||||
fi |
|
||||||
echo $sketch >> sketches.txt |
|
||||||
sketchnum=$(($sketchnum + 1)) |
|
||||||
done |
|
||||||
return $sketchnum |
|
||||||
} |
|
||||||
|
|
||||||
function build_sketches() # build_sketches <fqbn> <examples-path> <chunk> <total-chunks> [extra-options] |
|
||||||
{ |
|
||||||
local fqbn=$1 |
|
||||||
local examples=$2 |
|
||||||
local chunk_idex=$3 |
|
||||||
local chunks_num=$4 |
|
||||||
local xtra_opts=$5 |
|
||||||
|
|
||||||
if [ "$#" -lt 2 ]; then |
|
||||||
echo "ERROR: Illegal number of parameters" |
|
||||||
echo "USAGE: build_sketches <fqbn> <examples-path> [<chunk> <total-chunks>] [extra-options]" |
|
||||||
return 1 |
|
||||||
fi |
|
||||||
|
|
||||||
if [ "$#" -lt 4 ]; then |
|
||||||
chunk_idex="0" |
|
||||||
chunks_num="1" |
|
||||||
xtra_opts=$3 |
|
||||||
fi |
|
||||||
|
|
||||||
if [ "$chunks_num" -le 0 ]; then |
|
||||||
echo "ERROR: Chunks count must be positive number" |
|
||||||
return 1 |
|
||||||
fi |
|
||||||
if [ "$chunk_idex" -ge "$chunks_num" ]; then |
|
||||||
echo "ERROR: Chunk index must be less than chunks count" |
|
||||||
return 1 |
|
||||||
fi |
|
||||||
|
|
||||||
set +e |
|
||||||
count_sketches "$examples" |
|
||||||
local sketchcount=$? |
|
||||||
set -e |
|
||||||
local sketches=$(cat sketches.txt) |
|
||||||
rm -rf sketches.txt |
|
||||||
|
|
||||||
local chunk_size=$(( $sketchcount / $chunks_num )) |
|
||||||
local all_chunks=$(( $chunks_num * $chunk_size )) |
|
||||||
if [ "$all_chunks" -lt "$sketchcount" ]; then |
|
||||||
chunk_size=$(( $chunk_size + 1 )) |
|
||||||
fi |
|
||||||
|
|
||||||
local start_index=$(( $chunk_idex * $chunk_size )) |
|
||||||
if [ "$sketchcount" -le "$start_index" ]; then |
|
||||||
echo "Skipping job" |
|
||||||
return 0 |
|
||||||
fi |
|
||||||
|
|
||||||
local end_index=$(( $(( $chunk_idex + 1 )) * $chunk_size )) |
|
||||||
if [ "$end_index" -gt "$sketchcount" ]; then |
|
||||||
end_index=$sketchcount |
|
||||||
fi |
|
||||||
|
|
||||||
local start_num=$(( $start_index + 1 )) |
|
||||||
echo "Found $sketchcount Sketches"; |
|
||||||
echo "Chunk Count : $chunks_num" |
|
||||||
echo "Chunk Size : $chunk_size" |
|
||||||
echo "Start Sketch: $start_num" |
|
||||||
echo "End Sketch : $end_index" |
|
||||||
|
|
||||||
local sketchnum=0 |
|
||||||
for sketch in $sketches; do |
|
||||||
local sketchdir=$(dirname $sketch) |
|
||||||
local sketchdirname=$(basename $sketchdir) |
|
||||||
local sketchname=$(basename $sketch) |
|
||||||
if [ "${sketchdirname}.ino" != "$sketchname" ] \ |
|
||||||
|| [ -f "$sketchdir/.test.skip" ]; then |
|
||||||
continue |
|
||||||
fi |
|
||||||
sketchnum=$(($sketchnum + 1)) |
|
||||||
if [ "$sketchnum" -le "$start_index" ] \ |
|
||||||
|| [ "$sketchnum" -gt "$end_index" ]; then |
|
||||||
continue |
|
||||||
fi |
|
||||||
local sketchBuildFlags="" |
|
||||||
if [ -f "$sketchdir/.test.build_flags" ]; then |
|
||||||
while read line; do |
|
||||||
sketchBuildFlags="$sketchBuildFlags $line" |
|
||||||
done < "$sketchdir/.test.build_flags" |
|
||||||
fi |
|
||||||
build_sketch "$fqbn" "$sketch" "$sketchBuildFlags" "$xtra_opts" |
|
||||||
local result=$? |
|
||||||
if [ $result -ne 0 ]; then |
|
||||||
return $result |
|
||||||
fi |
|
||||||
done |
|
||||||
return 0 |
|
||||||
} |
|
@ -1,140 +0,0 @@ |
|||||||
#!/bin/bash |
|
||||||
|
|
||||||
echo "Installing Python Wheel ..." |
|
||||||
pip install wheel > /dev/null 2>&1 |
|
||||||
|
|
||||||
echo "Installing PlatformIO ..." |
|
||||||
pip install -U platformio > /dev/null 2>&1 |
|
||||||
|
|
||||||
echo "PlatformIO has been installed" |
|
||||||
echo "" |
|
||||||
|
|
||||||
|
|
||||||
function build_pio_sketch(){ # build_pio_sketch <board> <path-to-ino> <build-flags> |
|
||||||
if [ "$#" -lt 3 ]; then |
|
||||||
echo "ERROR: Illegal number of parameters" |
|
||||||
echo "USAGE: build_pio_sketch <board> <path-to-ino> <build-flags>" |
|
||||||
return 1 |
|
||||||
fi |
|
||||||
|
|
||||||
local board="$1" |
|
||||||
local sketch="$2" |
|
||||||
local buildFlags="$3" |
|
||||||
local sketch_dir=$(dirname "$sketch") |
|
||||||
echo "" |
|
||||||
echo "Compiling '"$(basename "$sketch")"' ..." |
|
||||||
python -m platformio ci -l '.' --board "$board" "$sketch_dir" --project-option="board_build.partitions = huge_app.csv" --project-option="build_flags=$buildFlags" |
|
||||||
} |
|
||||||
|
|
||||||
function count_sketches() # count_sketches <examples-path> |
|
||||||
{ |
|
||||||
local examples="$1" |
|
||||||
rm -rf sketches.txt |
|
||||||
if [ ! -d "$examples" ]; then |
|
||||||
touch sketches.txt |
|
||||||
return 0 |
|
||||||
fi |
|
||||||
local sketches=$(find $examples -name *.ino) |
|
||||||
local sketchnum=0 |
|
||||||
for sketch in $sketches; do |
|
||||||
local sketchdir=$(dirname $sketch) |
|
||||||
local sketchdirname=$(basename $sketchdir) |
|
||||||
local sketchname=$(basename $sketch) |
|
||||||
if [[ "${sketchdirname}.ino" != "$sketchname" ]]; then |
|
||||||
continue |
|
||||||
fi; |
|
||||||
if [[ -f "$sketchdir/.test.skip" ]]; then |
|
||||||
continue |
|
||||||
fi |
|
||||||
echo $sketch >> sketches.txt |
|
||||||
sketchnum=$(($sketchnum + 1)) |
|
||||||
done |
|
||||||
return $sketchnum |
|
||||||
} |
|
||||||
|
|
||||||
function build_pio_sketches() # build_pio_sketches <board> <examples-path> <chunk> <total-chunks> |
|
||||||
{ |
|
||||||
if [ "$#" -lt 2 ]; then |
|
||||||
echo "ERROR: Illegal number of parameters" |
|
||||||
echo "USAGE: build_pio_sketches <board> <examples-path> [<chunk> <total-chunks>]" |
|
||||||
return 1 |
|
||||||
fi |
|
||||||
|
|
||||||
local board=$1 |
|
||||||
local examples=$2 |
|
||||||
local chunk_idex=$3 |
|
||||||
local chunks_num=$4 |
|
||||||
|
|
||||||
if [ "$#" -lt 4 ]; then |
|
||||||
chunk_idex="0" |
|
||||||
chunks_num="1" |
|
||||||
fi |
|
||||||
|
|
||||||
if [ "$chunks_num" -le 0 ]; then |
|
||||||
echo "ERROR: Chunks count must be positive number" |
|
||||||
return 1 |
|
||||||
fi |
|
||||||
if [ "$chunk_idex" -ge "$chunks_num" ]; then |
|
||||||
echo "ERROR: Chunk index must be less than chunks count" |
|
||||||
return 1 |
|
||||||
fi |
|
||||||
|
|
||||||
set +e |
|
||||||
count_sketches "$examples" |
|
||||||
local sketchcount=$? |
|
||||||
set -e |
|
||||||
local sketches=$(cat sketches.txt) |
|
||||||
rm -rf sketches.txt |
|
||||||
|
|
||||||
local chunk_size=$(( $sketchcount / $chunks_num )) |
|
||||||
local all_chunks=$(( $chunks_num * $chunk_size )) |
|
||||||
if [ "$all_chunks" -lt "$sketchcount" ]; then |
|
||||||
chunk_size=$(( $chunk_size + 1 )) |
|
||||||
fi |
|
||||||
|
|
||||||
local start_index=$(( $chunk_idex * $chunk_size )) |
|
||||||
if [ "$sketchcount" -le "$start_index" ]; then |
|
||||||
echo "Skipping job" |
|
||||||
return 0 |
|
||||||
fi |
|
||||||
|
|
||||||
local end_index=$(( $(( $chunk_idex + 1 )) * $chunk_size )) |
|
||||||
if [ "$end_index" -gt "$sketchcount" ]; then |
|
||||||
end_index=$sketchcount |
|
||||||
fi |
|
||||||
|
|
||||||
local start_num=$(( $start_index + 1 )) |
|
||||||
echo "Found $sketchcount Sketches"; |
|
||||||
echo "Chunk Count : $chunks_num" |
|
||||||
echo "Chunk Size : $chunk_size" |
|
||||||
echo "Start Sketch: $start_num" |
|
||||||
echo "End Sketch : $end_index" |
|
||||||
|
|
||||||
local sketchnum=0 |
|
||||||
for sketch in $sketches; do |
|
||||||
local sketchdir=$(dirname $sketch) |
|
||||||
local sketchdirname=$(basename $sketchdir) |
|
||||||
local sketchname=$(basename $sketch) |
|
||||||
if [ "${sketchdirname}.ino" != "$sketchname" ] \ |
|
||||||
|| [ -f "$sketchdir/.test.skip" ]; then |
|
||||||
continue |
|
||||||
fi |
|
||||||
local sketchBuildFlags="" |
|
||||||
if [ -f "$sketchdir/.test.build_flags" ]; then |
|
||||||
while read line; do |
|
||||||
sketchBuildFlags="$sketchBuildFlags $line" |
|
||||||
done < "$sketchdir/.test.build_flags" |
|
||||||
fi |
|
||||||
sketchnum=$(($sketchnum + 1)) |
|
||||||
if [ "$sketchnum" -le "$start_index" ] \ |
|
||||||
|| [ "$sketchnum" -gt "$end_index" ]; then |
|
||||||
continue |
|
||||||
fi |
|
||||||
build_pio_sketch "$board" "$sketch" "$sketchBuildFlags" |
|
||||||
local result=$? |
|
||||||
if [ $result -ne 0 ]; then |
|
||||||
return $result |
|
||||||
fi |
|
||||||
done |
|
||||||
return 0 |
|
||||||
} |
|
@ -1,71 +0,0 @@ |
|||||||
#!/bin/bash |
|
||||||
|
|
||||||
set -e |
|
||||||
|
|
||||||
if [ ! -z "$TRAVIS_BUILD_DIR" ]; then |
|
||||||
export GITHUB_WORKSPACE="$TRAVIS_BUILD_DIR" |
|
||||||
export GITHUB_REPOSITORY="$TRAVIS_REPO_SLUG" |
|
||||||
elif [ -z "$GITHUB_WORKSPACE" ]; then |
|
||||||
export GITHUB_WORKSPACE="$PWD" |
|
||||||
export GITHUB_REPOSITORY="me-no-dev/ESPAsyncWebServer" |
|
||||||
fi |
|
||||||
|
|
||||||
TARGET_PLATFORM="$1" |
|
||||||
CHUNK_INDEX=$2 |
|
||||||
CHUNKS_CNT=$3 |
|
||||||
BUILD_PIO=0 |
|
||||||
if [ "$#" -lt 1 ]; then |
|
||||||
TARGET_PLATFORM="esp32" |
|
||||||
fi |
|
||||||
if [ "$#" -lt 3 ] || [ "$CHUNKS_CNT" -le 0 ]; then |
|
||||||
CHUNK_INDEX=0 |
|
||||||
CHUNKS_CNT=1 |
|
||||||
elif [ "$CHUNK_INDEX" -gt "$CHUNKS_CNT" ]; then |
|
||||||
CHUNK_INDEX=$CHUNKS_CNT |
|
||||||
elif [ "$CHUNK_INDEX" -eq "$CHUNKS_CNT" ]; then |
|
||||||
BUILD_PIO=1 |
|
||||||
fi |
|
||||||
|
|
||||||
if [ "$BUILD_PIO" -eq 0 ]; then |
|
||||||
# ArduinoIDE Test |
|
||||||
source ./.github/scripts/install-arduino-ide.sh |
|
||||||
|
|
||||||
echo "Installing ESPAsyncWebServer ..." |
|
||||||
cp -rf "$GITHUB_WORKSPACE" "$ARDUINO_USR_PATH/libraries/ESPAsyncWebServer" |
|
||||||
echo "Installing ArduinoJson ..." |
|
||||||
git clone https://github.com/bblanchon/ArduinoJson "$ARDUINO_USR_PATH/libraries/ArduinoJson" > /dev/null 2>&1 |
|
||||||
|
|
||||||
if [[ "$TARGET_PLATFORM" == "esp32" ]]; then |
|
||||||
echo "Installing AsyncTCP ..." |
|
||||||
git clone https://github.com/me-no-dev/AsyncTCP "$ARDUINO_USR_PATH/libraries/AsyncTCP" > /dev/null 2>&1 |
|
||||||
FQBN="espressif:esp32:esp32:PSRAM=enabled,PartitionScheme=huge_app" |
|
||||||
source ./.github/scripts/install-arduino-core-esp32.sh |
|
||||||
echo "BUILDING ESP32 EXAMPLES" |
|
||||||
else |
|
||||||
echo "Installing ESPAsyncTCP ..." |
|
||||||
git clone https://github.com/me-no-dev/ESPAsyncTCP "$ARDUINO_USR_PATH/libraries/ESPAsyncTCP" > /dev/null 2>&1 |
|
||||||
FQBN="esp8266com:esp8266:generic:eesz=4M1M,ip=lm2f" |
|
||||||
source ./.github/scripts/install-arduino-core-esp8266.sh |
|
||||||
echo "BUILDING ESP8266 EXAMPLES" |
|
||||||
fi |
|
||||||
build_sketches "$FQBN" "$GITHUB_WORKSPACE/examples" "$CHUNK_INDEX" "$CHUNKS_CNT" |
|
||||||
else |
|
||||||
# PlatformIO Test |
|
||||||
source ./.github/scripts/install-platformio.sh |
|
||||||
|
|
||||||
python -m platformio lib --storage-dir "$GITHUB_WORKSPACE" install |
|
||||||
echo "Installing ArduinoJson ..." |
|
||||||
python -m platformio lib -g install https://github.com/bblanchon/ArduinoJson.git > /dev/null 2>&1 |
|
||||||
if [[ "$TARGET_PLATFORM" == "esp32" ]]; then |
|
||||||
BOARD="esp32dev" |
|
||||||
echo "Installing AsyncTCP ..." |
|
||||||
python -m platformio lib -g install https://github.com/me-no-dev/AsyncTCP.git > /dev/null 2>&1 |
|
||||||
echo "BUILDING ESP32 EXAMPLES" |
|
||||||
else |
|
||||||
BOARD="esp12e" |
|
||||||
echo "Installing ESPAsyncTCP ..." |
|
||||||
python -m platformio lib -g install https://github.com/me-no-dev/ESPAsyncTCP.git > /dev/null 2>&1 |
|
||||||
echo "BUILDING ESP8266 EXAMPLES" |
|
||||||
fi |
|
||||||
build_pio_sketches "$BOARD" "$GITHUB_WORKSPACE/examples" |
|
||||||
fi |
|
@ -1,31 +0,0 @@ |
|||||||
# Configuration for probot-stale - https://github.com/probot/stale |
|
||||||
|
|
||||||
daysUntilStale: 60 |
|
||||||
daysUntilClose: 14 |
|
||||||
limitPerRun: 30 |
|
||||||
staleLabel: stale |
|
||||||
exemptLabels: |
|
||||||
- pinned |
|
||||||
- security |
|
||||||
- "to be implemented" |
|
||||||
- "for reference" |
|
||||||
- "move to PR" |
|
||||||
- "enhancement" |
|
||||||
|
|
||||||
only: issues |
|
||||||
onlyLabels: [] |
|
||||||
exemptProjects: false |
|
||||||
exemptMilestones: false |
|
||||||
exemptAssignees: false |
|
||||||
|
|
||||||
markComment: > |
|
||||||
[STALE_SET] This issue has been automatically marked as stale because it has not had |
|
||||||
recent activity. It will be closed in 14 days if no further activity occurs. Thank you |
|
||||||
for your contributions. |
|
||||||
|
|
||||||
unmarkComment: > |
|
||||||
[STALE_CLR] This issue has been removed from the stale queue. Please ensure activity to keep it openin the future. |
|
||||||
|
|
||||||
closeComment: > |
|
||||||
[STALE_DEL] This stale issue has been automatically closed. Thank you for your contributions. |
|
||||||
|
|
@ -1,34 +0,0 @@ |
|||||||
name: ESP Async Web Server CI |
|
||||||
|
|
||||||
on: |
|
||||||
push: |
|
||||||
branches: |
|
||||||
- master |
|
||||||
- release/* |
|
||||||
pull_request: |
|
||||||
|
|
||||||
jobs: |
|
||||||
|
|
||||||
build-arduino: |
|
||||||
name: Arduino for ${{ matrix.board }} on ${{ matrix.os }} |
|
||||||
runs-on: ${{ matrix.os }} |
|
||||||
strategy: |
|
||||||
matrix: |
|
||||||
os: [ubuntu-latest, windows-latest, macOS-latest] |
|
||||||
board: [esp32, esp8266] |
|
||||||
steps: |
|
||||||
- uses: actions/checkout@v1 |
|
||||||
- name: Build Tests |
|
||||||
run: bash ./.github/scripts/on-push.sh ${{ matrix.board }} 0 1 |
|
||||||
|
|
||||||
# build-pio: |
|
||||||
# name: PlatformIO for ${{ matrix.board }} on ${{ matrix.os }} |
|
||||||
# runs-on: ${{ matrix.os }} |
|
||||||
# strategy: |
|
||||||
# matrix: |
|
||||||
# os: [ubuntu-latest, windows-latest, macOS-latest] |
|
||||||
# board: [esp32, esp8266] |
|
||||||
# steps: |
|
||||||
# - uses: actions/checkout@v1 |
|
||||||
# - name: Build Tests |
|
||||||
# run: bash ./.github/scripts/on-push.sh ${{ matrix.board }} 1 1 |
|
@ -1,2 +0,0 @@ |
|||||||
.vscode |
|
||||||
.DS_Store |
|
@ -1,46 +0,0 @@ |
|||||||
sudo: false |
|
||||||
|
|
||||||
language: python |
|
||||||
|
|
||||||
os: |
|
||||||
- linux |
|
||||||
|
|
||||||
git: |
|
||||||
depth: false |
|
||||||
|
|
||||||
stages: |
|
||||||
- build |
|
||||||
|
|
||||||
jobs: |
|
||||||
include: |
|
||||||
|
|
||||||
- name: "Build Arduino ESP32" |
|
||||||
if: tag IS blank AND (type = pull_request OR (type = push AND branch = master)) |
|
||||||
stage: build |
|
||||||
script: bash $TRAVIS_BUILD_DIR/.github/scripts/on-push.sh esp32 |
|
||||||
|
|
||||||
- name: "Build Arduino ESP8266" |
|
||||||
if: tag IS blank AND (type = pull_request OR (type = push AND branch = master)) |
|
||||||
stage: build |
|
||||||
script: bash $TRAVIS_BUILD_DIR/.github/scripts/on-push.sh esp8266 |
|
||||||
|
|
||||||
- name: "Build Platformio ESP32" |
|
||||||
if: tag IS blank AND (type = pull_request OR (type = push AND branch = master)) |
|
||||||
stage: build |
|
||||||
script: bash $TRAVIS_BUILD_DIR/.github/scripts/on-push.sh esp32 1 1 |
|
||||||
|
|
||||||
- name: "Build Platformio ESP8266" |
|
||||||
if: tag IS blank AND (type = pull_request OR (type = push AND branch = master)) |
|
||||||
stage: build |
|
||||||
script: bash $TRAVIS_BUILD_DIR/.github/scripts/on-push.sh esp8266 1 1 |
|
||||||
|
|
||||||
notifications: |
|
||||||
email: |
|
||||||
on_success: change |
|
||||||
on_failure: change |
|
||||||
webhooks: |
|
||||||
urls: |
|
||||||
- https://webhooks.gitter.im/e/60e65d0c78ea0a920347 |
|
||||||
on_success: change # options: [always|never|change] default: always |
|
||||||
on_failure: always # options: [always|never|change] default: always |
|
||||||
on_start: never # options: [always|never|change] default: always |
|
@ -1,17 +0,0 @@ |
|||||||
set(COMPONENT_SRCDIRS |
|
||||||
"src" |
|
||||||
) |
|
||||||
|
|
||||||
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) |
|
File diff suppressed because it is too large
Load Diff
@ -1 +0,0 @@ |
|||||||
theme: jekyll-theme-cayman |
|
@ -1,3 +0,0 @@ |
|||||||
COMPONENT_ADD_INCLUDEDIRS := src
|
|
||||||
COMPONENT_SRCDIRS := src
|
|
||||||
CXXFLAGS += -fno-rtti
|
|
@ -1,47 +0,0 @@ |
|||||||
#include <DNSServer.h> |
|
||||||
#ifdef ESP32 |
|
||||||
#include <WiFi.h> |
|
||||||
#include <AsyncTCP.h> |
|
||||||
#elif defined(ESP8266) |
|
||||||
#include <ESP8266WiFi.h> |
|
||||||
#include <ESPAsyncTCP.h> |
|
||||||
#endif |
|
||||||
#include "ESPAsyncWebServer.h" |
|
||||||
|
|
||||||
DNSServer dnsServer; |
|
||||||
AsyncWebServer server(80); |
|
||||||
|
|
||||||
class CaptiveRequestHandler : public AsyncWebHandler { |
|
||||||
public: |
|
||||||
CaptiveRequestHandler() {} |
|
||||||
virtual ~CaptiveRequestHandler() {} |
|
||||||
|
|
||||||
bool canHandle(AsyncWebServerRequest *request){ |
|
||||||
//request->addInterestingHeader("ANY");
|
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
void handleRequest(AsyncWebServerRequest *request) { |
|
||||||
AsyncResponseStream *response = request->beginResponseStream("text/html"); |
|
||||||
response->print("<!DOCTYPE html><html><head><title>Captive Portal</title></head><body>"); |
|
||||||
response->print("<p>This is out captive portal front page.</p>"); |
|
||||||
response->printf("<p>You were trying to reach: http://%s%s</p>", request->host().c_str(), request->url().c_str()); |
|
||||||
response->printf("<p>Try opening <a href='http://%s'>this link</a> instead</p>", WiFi.softAPIP().toString().c_str()); |
|
||||||
response->print("</body></html>"); |
|
||||||
request->send(response); |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
|
|
||||||
void setup(){ |
|
||||||
//your other setup stuff...
|
|
||||||
WiFi.softAP("esp-captive"); |
|
||||||
dnsServer.start(53, "*", WiFi.softAPIP()); |
|
||||||
server.addHandler(new CaptiveRequestHandler()).setFilter(ON_AP_FILTER);//only when requested from AP
|
|
||||||
//more handlers...
|
|
||||||
server.begin(); |
|
||||||
} |
|
||||||
|
|
||||||
void loop(){ |
|
||||||
dnsServer.processNextRequest(); |
|
||||||
} |
|
@ -1,221 +0,0 @@ |
|||||||
#include <ArduinoOTA.h> |
|
||||||
#ifdef ESP32 |
|
||||||
#include <FS.h> |
|
||||||
#include <SPIFFS.h> |
|
||||||
#include <ESPmDNS.h> |
|
||||||
#include <WiFi.h> |
|
||||||
#include <AsyncTCP.h> |
|
||||||
#elif defined(ESP8266) |
|
||||||
#include <ESP8266WiFi.h> |
|
||||||
#include <ESPAsyncTCP.h> |
|
||||||
#include <ESP8266mDNS.h> |
|
||||||
#endif |
|
||||||
#include <ESPAsyncWebServer.h> |
|
||||||
#include <SPIFFSEditor.h> |
|
||||||
|
|
||||||
// SKETCH BEGIN
|
|
||||||
AsyncWebServer server(80); |
|
||||||
AsyncWebSocket ws("/ws"); |
|
||||||
AsyncEventSource events("/events"); |
|
||||||
|
|
||||||
void onWsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){ |
|
||||||
if(type == WS_EVT_CONNECT){ |
|
||||||
Serial.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){ |
|
||||||
Serial.printf("ws[%s][%u] disconnect\n", server->url(), client->id()); |
|
||||||
} else if(type == WS_EVT_ERROR){ |
|
||||||
Serial.printf("ws[%s][%u] error(%u): %s\n", server->url(), client->id(), *((uint16_t*)arg), (char*)data); |
|
||||||
} else if(type == WS_EVT_PONG){ |
|
||||||
Serial.printf("ws[%s][%u] pong[%u]: %s\n", server->url(), client->id(), len, (len)?(char*)data:""); |
|
||||||
} else if(type == WS_EVT_DATA){ |
|
||||||
AwsFrameInfo * info = (AwsFrameInfo*)arg; |
|
||||||
String msg = ""; |
|
||||||
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
|
|
||||||
Serial.printf("ws[%s][%u] %s-message[%llu]: ", server->url(), client->id(), (info->opcode == WS_TEXT)?"text":"binary", info->len); |
|
||||||
|
|
||||||
if(info->opcode == WS_TEXT){ |
|
||||||
for(size_t i=0; i < info->len; i++) { |
|
||||||
msg += (char) data[i]; |
|
||||||
} |
|
||||||
} else { |
|
||||||
char buff[3]; |
|
||||||
for(size_t i=0; i < info->len; i++) { |
|
||||||
sprintf(buff, "%02x ", (uint8_t) data[i]); |
|
||||||
msg += buff ; |
|
||||||
} |
|
||||||
} |
|
||||||
Serial.printf("%s\n",msg.c_str()); |
|
||||||
|
|
||||||
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) |
|
||||||
Serial.printf("ws[%s][%u] %s-message start\n", server->url(), client->id(), (info->message_opcode == WS_TEXT)?"text":"binary"); |
|
||||||
Serial.printf("ws[%s][%u] frame[%u] start[%llu]\n", server->url(), client->id(), info->num, info->len); |
|
||||||
} |
|
||||||
|
|
||||||
Serial.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->opcode == WS_TEXT){ |
|
||||||
for(size_t i=0; i < len; i++) { |
|
||||||
msg += (char) data[i]; |
|
||||||
} |
|
||||||
} else { |
|
||||||
char buff[3]; |
|
||||||
for(size_t i=0; i < len; i++) { |
|
||||||
sprintf(buff, "%02x ", (uint8_t) data[i]); |
|
||||||
msg += buff ; |
|
||||||
} |
|
||||||
} |
|
||||||
Serial.printf("%s\n",msg.c_str()); |
|
||||||
|
|
||||||
if((info->index + len) == info->len){ |
|
||||||
Serial.printf("ws[%s][%u] frame[%u] end[%llu]\n", server->url(), client->id(), info->num, info->len); |
|
||||||
if(info->final){ |
|
||||||
Serial.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"); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
const char* ssid = "*******"; |
|
||||||
const char* password = "*******"; |
|
||||||
const char * hostName = "esp-async"; |
|
||||||
const char* http_username = "admin"; |
|
||||||
const char* http_password = "admin"; |
|
||||||
|
|
||||||
void setup(){ |
|
||||||
Serial.begin(115200); |
|
||||||
Serial.setDebugOutput(true); |
|
||||||
WiFi.mode(WIFI_AP_STA); |
|
||||||
WiFi.softAP(hostName); |
|
||||||
WiFi.begin(ssid, password); |
|
||||||
if (WiFi.waitForConnectResult() != WL_CONNECTED) { |
|
||||||
Serial.printf("STA: Failed!\n"); |
|
||||||
WiFi.disconnect(false); |
|
||||||
delay(1000); |
|
||||||
WiFi.begin(ssid, password); |
|
||||||
} |
|
||||||
|
|
||||||
//Send OTA events to the browser
|
|
||||||
ArduinoOTA.onStart([]() { events.send("Update Start", "ota"); }); |
|
||||||
ArduinoOTA.onEnd([]() { events.send("Update End", "ota"); }); |
|
||||||
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { |
|
||||||
char p[32]; |
|
||||||
sprintf(p, "Progress: %u%%\n", (progress/(total/100))); |
|
||||||
events.send(p, "ota"); |
|
||||||
}); |
|
||||||
ArduinoOTA.onError([](ota_error_t error) { |
|
||||||
if(error == OTA_AUTH_ERROR) events.send("Auth Failed", "ota"); |
|
||||||
else if(error == OTA_BEGIN_ERROR) events.send("Begin Failed", "ota"); |
|
||||||
else if(error == OTA_CONNECT_ERROR) events.send("Connect Failed", "ota"); |
|
||||||
else if(error == OTA_RECEIVE_ERROR) events.send("Recieve Failed", "ota"); |
|
||||||
else if(error == OTA_END_ERROR) events.send("End Failed", "ota"); |
|
||||||
}); |
|
||||||
ArduinoOTA.setHostname(hostName); |
|
||||||
ArduinoOTA.begin(); |
|
||||||
|
|
||||||
MDNS.addService("http","tcp",80); |
|
||||||
|
|
||||||
SPIFFS.begin(); |
|
||||||
|
|
||||||
ws.onEvent(onWsEvent); |
|
||||||
server.addHandler(&ws); |
|
||||||
|
|
||||||
events.onConnect([](AsyncEventSourceClient *client){ |
|
||||||
client->send("hello!",NULL,millis(),1000); |
|
||||||
}); |
|
||||||
server.addHandler(&events); |
|
||||||
|
|
||||||
#ifdef ESP32 |
|
||||||
server.addHandler(new SPIFFSEditor(SPIFFS, http_username,http_password)); |
|
||||||
#elif defined(ESP8266) |
|
||||||
server.addHandler(new SPIFFSEditor(http_username,http_password)); |
|
||||||
#endif |
|
||||||
|
|
||||||
server.on("/heap", HTTP_GET, [](AsyncWebServerRequest *request){ |
|
||||||
request->send(200, "text/plain", String(ESP.getFreeHeap())); |
|
||||||
}); |
|
||||||
|
|
||||||
server.serveStatic("/", SPIFFS, "/").setDefaultFile("index.htm"); |
|
||||||
|
|
||||||
server.onNotFound([](AsyncWebServerRequest *request){ |
|
||||||
Serial.printf("NOT_FOUND: "); |
|
||||||
if(request->method() == HTTP_GET) |
|
||||||
Serial.printf("GET"); |
|
||||||
else if(request->method() == HTTP_POST) |
|
||||||
Serial.printf("POST"); |
|
||||||
else if(request->method() == HTTP_DELETE) |
|
||||||
Serial.printf("DELETE"); |
|
||||||
else if(request->method() == HTTP_PUT) |
|
||||||
Serial.printf("PUT"); |
|
||||||
else if(request->method() == HTTP_PATCH) |
|
||||||
Serial.printf("PATCH"); |
|
||||||
else if(request->method() == HTTP_HEAD) |
|
||||||
Serial.printf("HEAD"); |
|
||||||
else if(request->method() == HTTP_OPTIONS) |
|
||||||
Serial.printf("OPTIONS"); |
|
||||||
else |
|
||||||
Serial.printf("UNKNOWN"); |
|
||||||
Serial.printf(" http://%s%s\n", request->host().c_str(), request->url().c_str()); |
|
||||||
|
|
||||||
if(request->contentLength()){ |
|
||||||
Serial.printf("_CONTENT_TYPE: %s\n", request->contentType().c_str()); |
|
||||||
Serial.printf("_CONTENT_LENGTH: %u\n", request->contentLength()); |
|
||||||
} |
|
||||||
|
|
||||||
int headers = request->headers(); |
|
||||||
int i; |
|
||||||
for(i=0;i<headers;i++){ |
|
||||||
AsyncWebHeader* h = request->getHeader(i); |
|
||||||
Serial.printf("_HEADER[%s]: %s\n", h->name().c_str(), h->value().c_str()); |
|
||||||
} |
|
||||||
|
|
||||||
int params = request->params(); |
|
||||||
for(i=0;i<params;i++){ |
|
||||||
AsyncWebParameter* p = request->getParam(i); |
|
||||||
if(p->isFile()){ |
|
||||||
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()); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
request->send(404); |
|
||||||
}); |
|
||||||
server.onFileUpload([](AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final){ |
|
||||||
if(!index) |
|
||||||
Serial.printf("UploadStart: %s\n", filename.c_str()); |
|
||||||
Serial.printf("%s", (const char*)data); |
|
||||||
if(final) |
|
||||||
Serial.printf("UploadEnd: %s (%u)\n", filename.c_str(), index+len); |
|
||||||
}); |
|
||||||
server.onRequestBody([](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total){ |
|
||||||
if(!index) |
|
||||||
Serial.printf("BodyStart: %u\n", total); |
|
||||||
Serial.printf("%s", (const char*)data); |
|
||||||
if(index + len == total) |
|
||||||
Serial.printf("BodyEnd: %u\n", total); |
|
||||||
}); |
|
||||||
server.begin(); |
|
||||||
} |
|
||||||
|
|
||||||
void loop(){ |
|
||||||
ArduinoOTA.handle(); |
|
||||||
ws.cleanupClients(); |
|
||||||
} |
|
@ -1,2 +0,0 @@ |
|||||||
/*.js.gz |
|
||||||
/.exclude.files |
|
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 1.1 KiB |
@ -1,131 +0,0 @@ |
|||||||
<!-- |
|
||||||
FSWebServer - Example Index Page |
|
||||||
Copyright (c) 2015 Hristo Gochkov. All rights reserved. |
|
||||||
This file is part of the ESP8266WebServer library for Arduino environment. |
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or |
|
||||||
modify it under the terms of the GNU Lesser General Public |
|
||||||
License as published by the Free Software Foundation; either |
|
||||||
version 2.1 of the License, or (at your option) any later version. |
|
||||||
This library is distributed in the hope that it will be useful, |
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
||||||
Lesser General Public License for more details. |
|
||||||
You should have received a copy of the GNU Lesser General Public |
|
||||||
License along with this library; if not, write to the Free Software |
|
||||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
|
||||||
--> |
|
||||||
<!DOCTYPE html> |
|
||||||
<html> |
|
||||||
<head> |
|
||||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8"> |
|
||||||
<title>WebSocketTester</title> |
|
||||||
<style type="text/css" media="screen"> |
|
||||||
body { |
|
||||||
margin:0; |
|
||||||
padding:0; |
|
||||||
background-color: black; |
|
||||||
} |
|
||||||
|
|
||||||
#dbg, #input_div, #input_el { |
|
||||||
font-family: monaco; |
|
||||||
font-size: 12px; |
|
||||||
line-height: 13px; |
|
||||||
color: #AAA; |
|
||||||
} |
|
||||||
|
|
||||||
#dbg, #input_div { |
|
||||||
margin:0; |
|
||||||
padding:0; |
|
||||||
padding-left:4px; |
|
||||||
} |
|
||||||
|
|
||||||
#input_el { |
|
||||||
width:98%; |
|
||||||
background-color: rgba(0,0,0,0); |
|
||||||
border: 0px; |
|
||||||
} |
|
||||||
#input_el:focus { |
|
||||||
outline: none; |
|
||||||
} |
|
||||||
</style> |
|
||||||
<script type="text/javascript"> |
|
||||||
var ws = null; |
|
||||||
function ge(s){ return document.getElementById(s);} |
|
||||||
function ce(s){ return document.createElement(s);} |
|
||||||
function stb(){ window.scrollTo(0, document.body.scrollHeight || document.documentElement.scrollHeight); } |
|
||||||
function sendBlob(str){ |
|
||||||
var buf = new Uint8Array(str.length); |
|
||||||
for (var i = 0; i < str.length; ++i) buf[i] = str.charCodeAt(i); |
|
||||||
ws.send(buf); |
|
||||||
} |
|
||||||
function addMessage(m){ |
|
||||||
var msg = ce("div"); |
|
||||||
msg.innerText = m; |
|
||||||
ge("dbg").appendChild(msg); |
|
||||||
stb(); |
|
||||||
} |
|
||||||
function startSocket(){ |
|
||||||
ws = new WebSocket('ws://'+document.location.host+'/ws',['arduino']); |
|
||||||
ws.binaryType = "arraybuffer"; |
|
||||||
ws.onopen = function(e){ |
|
||||||
addMessage("Connected"); |
|
||||||
}; |
|
||||||
ws.onclose = function(e){ |
|
||||||
addMessage("Disconnected"); |
|
||||||
}; |
|
||||||
ws.onerror = function(e){ |
|
||||||
console.log("ws error", e); |
|
||||||
addMessage("Error"); |
|
||||||
}; |
|
||||||
ws.onmessage = function(e){ |
|
||||||
var msg = ""; |
|
||||||
if(e.data instanceof ArrayBuffer){ |
|
||||||
msg = "BIN:"; |
|
||||||
var bytes = new Uint8Array(e.data); |
|
||||||
for (var i = 0; i < bytes.length; i++) { |
|
||||||
msg += String.fromCharCode(bytes[i]); |
|
||||||
} |
|
||||||
} else { |
|
||||||
msg = "TXT:"+e.data; |
|
||||||
} |
|
||||||
addMessage(msg); |
|
||||||
}; |
|
||||||
ge("input_el").onkeydown = function(e){ |
|
||||||
stb(); |
|
||||||
if(e.keyCode == 13 && ge("input_el").value != ""){ |
|
||||||
ws.send(ge("input_el").value); |
|
||||||
ge("input_el").value = ""; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
function startEvents(){ |
|
||||||
var es = new EventSource('/events'); |
|
||||||
es.onopen = function(e) { |
|
||||||
addMessage("Events Opened"); |
|
||||||
}; |
|
||||||
es.onerror = function(e) { |
|
||||||
if (e.target.readyState != EventSource.OPEN) { |
|
||||||
addMessage("Events Closed"); |
|
||||||
} |
|
||||||
}; |
|
||||||
es.onmessage = function(e) { |
|
||||||
addMessage("Event: " + e.data); |
|
||||||
}; |
|
||||||
es.addEventListener('ota', function(e) { |
|
||||||
addMessage("Event[ota]: " + e.data); |
|
||||||
}, false); |
|
||||||
} |
|
||||||
function onBodyLoad(){ |
|
||||||
startSocket(); |
|
||||||
startEvents(); |
|
||||||
} |
|
||||||
</script> |
|
||||||
</head> |
|
||||||
<body id="body" onload="onBodyLoad()"> |
|
||||||
<pre id="dbg"></pre> |
|
||||||
<div id="input_div"> |
|
||||||
$<input type="text" value="" id="input_el"> |
|
||||||
</div> |
|
||||||
</body> |
|
||||||
</html> |
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1 +0,0 @@ |
|||||||
-DASYNCWEBSERVER_REGEX=1 |
|
@ -1,77 +0,0 @@ |
|||||||
//
|
|
||||||
// A simple server implementation with regex routes:
|
|
||||||
// * serve static messages
|
|
||||||
// * read GET and POST parameters
|
|
||||||
// * handle missing pages / 404s
|
|
||||||
//
|
|
||||||
|
|
||||||
// Add buildflag ASYNCWEBSERVER_REGEX to enable the regex support
|
|
||||||
|
|
||||||
// For platformio: platformio.ini:
|
|
||||||
// build_flags =
|
|
||||||
// -DASYNCWEBSERVER_REGEX
|
|
||||||
|
|
||||||
// For arduino IDE: create/update platform.local.txt
|
|
||||||
// Windows: C:\Users\(username)\AppData\Local\Arduino15\packages\espxxxx\hardware\espxxxx\{version}\platform.local.txt
|
|
||||||
// Linux: ~/.arduino15/packages/espxxxx/hardware/espxxxx/{version}/platform.local.txt
|
|
||||||
//
|
|
||||||
// compiler.cpp.extra_flags=-DASYNCWEBSERVER_REGEX=1
|
|
||||||
|
|
||||||
#include <Arduino.h> |
|
||||||
#ifdef ESP32 |
|
||||||
#include <WiFi.h> |
|
||||||
#include <AsyncTCP.h> |
|
||||||
#elif defined(ESP8266) |
|
||||||
#include <ESP8266WiFi.h> |
|
||||||
#include <ESPAsyncTCP.h> |
|
||||||
#endif |
|
||||||
#include <ESPAsyncWebServer.h> |
|
||||||
|
|
||||||
AsyncWebServer server(80); |
|
||||||
|
|
||||||
const char* ssid = "YOUR_SSID"; |
|
||||||
const char* password = "YOUR_PASSWORD"; |
|
||||||
|
|
||||||
const char* PARAM_MESSAGE = "message"; |
|
||||||
|
|
||||||
void notFound(AsyncWebServerRequest *request) { |
|
||||||
request->send(404, "text/plain", "Not found"); |
|
||||||
} |
|
||||||
|
|
||||||
void setup() { |
|
||||||
|
|
||||||
Serial.begin(115200); |
|
||||||
WiFi.mode(WIFI_STA); |
|
||||||
WiFi.begin(ssid, password); |
|
||||||
if (WiFi.waitForConnectResult() != WL_CONNECTED) { |
|
||||||
Serial.printf("WiFi Failed!\n"); |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
Serial.print("IP Address: "); |
|
||||||
Serial.println(WiFi.localIP()); |
|
||||||
|
|
||||||
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ |
|
||||||
request->send(200, "text/plain", "Hello, world"); |
|
||||||
}); |
|
||||||
|
|
||||||
// Send a GET request to <IP>/sensor/<number>
|
|
||||||
server.on("^\\/sensor\\/([0-9]+)$", HTTP_GET, [] (AsyncWebServerRequest *request) { |
|
||||||
String sensorNumber = request->pathArg(0); |
|
||||||
request->send(200, "text/plain", "Hello, sensor: " + sensorNumber); |
|
||||||
}); |
|
||||||
|
|
||||||
// Send a GET request to <IP>/sensor/<number>/action/<action>
|
|
||||||
server.on("^\\/sensor\\/([0-9]+)\\/action\\/([a-zA-Z0-9]+)$", HTTP_GET, [] (AsyncWebServerRequest *request) { |
|
||||||
String sensorNumber = request->pathArg(0); |
|
||||||
String action = request->pathArg(1); |
|
||||||
request->send(200, "text/plain", "Hello, sensor: " + sensorNumber + ", with action: " + action); |
|
||||||
}); |
|
||||||
|
|
||||||
server.onNotFound(notFound); |
|
||||||
|
|
||||||
server.begin(); |
|
||||||
} |
|
||||||
|
|
||||||
void loop() { |
|
||||||
} |
|
@ -1,74 +0,0 @@ |
|||||||
//
|
|
||||||
// A simple server implementation showing how to:
|
|
||||||
// * serve static messages
|
|
||||||
// * read GET and POST parameters
|
|
||||||
// * handle missing pages / 404s
|
|
||||||
//
|
|
||||||
|
|
||||||
#include <Arduino.h> |
|
||||||
#ifdef ESP32 |
|
||||||
#include <WiFi.h> |
|
||||||
#include <AsyncTCP.h> |
|
||||||
#elif defined(ESP8266) |
|
||||||
#include <ESP8266WiFi.h> |
|
||||||
#include <ESPAsyncTCP.h> |
|
||||||
#endif |
|
||||||
#include <ESPAsyncWebServer.h> |
|
||||||
|
|
||||||
AsyncWebServer server(80); |
|
||||||
|
|
||||||
const char* ssid = "YOUR_SSID"; |
|
||||||
const char* password = "YOUR_PASSWORD"; |
|
||||||
|
|
||||||
const char* PARAM_MESSAGE = "message"; |
|
||||||
|
|
||||||
void notFound(AsyncWebServerRequest *request) { |
|
||||||
request->send(404, "text/plain", "Not found"); |
|
||||||
} |
|
||||||
|
|
||||||
void setup() { |
|
||||||
|
|
||||||
Serial.begin(115200); |
|
||||||
WiFi.mode(WIFI_STA); |
|
||||||
WiFi.begin(ssid, password); |
|
||||||
if (WiFi.waitForConnectResult() != WL_CONNECTED) { |
|
||||||
Serial.printf("WiFi Failed!\n"); |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
Serial.print("IP Address: "); |
|
||||||
Serial.println(WiFi.localIP()); |
|
||||||
|
|
||||||
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ |
|
||||||
request->send(200, "text/plain", "Hello, world"); |
|
||||||
}); |
|
||||||
|
|
||||||
// Send a GET request to <IP>/get?message=<message>
|
|
||||||
server.on("/get", HTTP_GET, [] (AsyncWebServerRequest *request) { |
|
||||||
String message; |
|
||||||
if (request->hasParam(PARAM_MESSAGE)) { |
|
||||||
message = request->getParam(PARAM_MESSAGE)->value(); |
|
||||||
} else { |
|
||||||
message = "No message sent"; |
|
||||||
} |
|
||||||
request->send(200, "text/plain", "Hello, GET: " + message); |
|
||||||
}); |
|
||||||
|
|
||||||
// Send a POST request to <IP>/post with a form field message set to <message>
|
|
||||||
server.on("/post", HTTP_POST, [](AsyncWebServerRequest *request){ |
|
||||||
String message; |
|
||||||
if (request->hasParam(PARAM_MESSAGE, true)) { |
|
||||||
message = request->getParam(PARAM_MESSAGE, true)->value(); |
|
||||||
} else { |
|
||||||
message = "No message sent"; |
|
||||||
} |
|
||||||
request->send(200, "text/plain", "Hello, POST: " + message); |
|
||||||
}); |
|
||||||
|
|
||||||
server.onNotFound(notFound); |
|
||||||
|
|
||||||
server.begin(); |
|
||||||
} |
|
||||||
|
|
||||||
void loop() { |
|
||||||
} |
|
@ -1,3 +0,0 @@ |
|||||||
JsonArray KEYWORD1 |
|
||||||
add KEYWORD2 |
|
||||||
createArray KEYWORD3 |
|
@ -1,33 +0,0 @@ |
|||||||
{ |
|
||||||
"name":"ESP Async WebServer", |
|
||||||
"description":"Asynchronous HTTP and WebSocket Server Library for ESP8266 and ESP32", |
|
||||||
"keywords":"http,async,websocket,webserver", |
|
||||||
"authors": |
|
||||||
{ |
|
||||||
"name": "Hristo Gochkov", |
|
||||||
"maintainer": true |
|
||||||
}, |
|
||||||
"repository": |
|
||||||
{ |
|
||||||
"type": "git", |
|
||||||
"url": "https://github.com/me-no-dev/ESPAsyncWebServer.git" |
|
||||||
}, |
|
||||||
"version": "1.2.3", |
|
||||||
"license": "LGPL-3.0", |
|
||||||
"frameworks": "arduino", |
|
||||||
"platforms": ["espressif8266", "espressif32"], |
|
||||||
"dependencies": [ |
|
||||||
{ |
|
||||||
"name": "ESPAsyncTCP", |
|
||||||
"platforms": "espressif8266" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "AsyncTCP", |
|
||||||
"platforms": "espressif32" |
|
||||||
}, |
|
||||||
{ |
|
||||||
"name": "Hash", |
|
||||||
"platforms": "espressif8266" |
|
||||||
} |
|
||||||
] |
|
||||||
} |
|
@ -1,9 +0,0 @@ |
|||||||
name=ESP Async WebServer |
|
||||||
version=1.2.3 |
|
||||||
author=Me-No-Dev |
|
||||||
maintainer=Me-No-Dev |
|
||||||
sentence=Async Web Server for ESP8266 and ESP31B |
|
||||||
paragraph=Async Web Server for ESP8266 and ESP31B |
|
||||||
category=Other |
|
||||||
url=https://github.com/me-no-dev/ESPAsyncWebServer |
|
||||||
architectures=* |
|
@ -1,368 +0,0 @@ |
|||||||
/*
|
|
||||||
Asynchronous WebServer library for Espressif MCUs |
|
||||||
|
|
||||||
Copyright (c) 2016 Hristo Gochkov. All rights reserved. |
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or |
|
||||||
modify it under the terms of the GNU Lesser General Public |
|
||||||
License as published by the Free Software Foundation; either |
|
||||||
version 2.1 of the License, or (at your option) any later version. |
|
||||||
|
|
||||||
This library is distributed in the hope that it will be useful, |
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
||||||
Lesser General Public License for more details. |
|
||||||
|
|
||||||
You should have received a copy of the GNU Lesser General Public |
|
||||||
License along with this library; if not, write to the Free Software |
|
||||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
|
||||||
*/ |
|
||||||
#include "Arduino.h" |
|
||||||
#include "AsyncEventSource.h" |
|
||||||
|
|
||||||
static String generateEventMessage(const char *message, const char *event, uint32_t id, uint32_t reconnect){ |
|
||||||
String ev = ""; |
|
||||||
|
|
||||||
if(reconnect){ |
|
||||||
ev += "retry: "; |
|
||||||
ev += String(reconnect); |
|
||||||
ev += "\r\n"; |
|
||||||
} |
|
||||||
|
|
||||||
if(id){ |
|
||||||
ev += "id: "; |
|
||||||
ev += String(id); |
|
||||||
ev += "\r\n"; |
|
||||||
} |
|
||||||
|
|
||||||
if(event != NULL){ |
|
||||||
ev += "event: "; |
|
||||||
ev += String(event); |
|
||||||
ev += "\r\n"; |
|
||||||
} |
|
||||||
|
|
||||||
if(message != NULL){ |
|
||||||
size_t messageLen = strlen(message); |
|
||||||
char * lineStart = (char *)message; |
|
||||||
char * lineEnd; |
|
||||||
do { |
|
||||||
char * nextN = strchr(lineStart, '\n'); |
|
||||||
char * nextR = strchr(lineStart, '\r'); |
|
||||||
if(nextN == NULL && nextR == NULL){ |
|
||||||
size_t llen = ((char *)message + messageLen) - lineStart; |
|
||||||
char * ldata = (char *)malloc(llen+1); |
|
||||||
if(ldata != NULL){ |
|
||||||
memcpy(ldata, lineStart, llen); |
|
||||||
ldata[llen] = 0; |
|
||||||
ev += "data: "; |
|
||||||
ev += ldata; |
|
||||||
ev += "\r\n\r\n"; |
|
||||||
free(ldata); |
|
||||||
} |
|
||||||
lineStart = (char *)message + messageLen; |
|
||||||
} else { |
|
||||||
char * nextLine = NULL; |
|
||||||
if(nextN != NULL && nextR != NULL){ |
|
||||||
if(nextR < nextN){ |
|
||||||
lineEnd = nextR; |
|
||||||
if(nextN == (nextR + 1)) |
|
||||||
nextLine = nextN + 1; |
|
||||||
else |
|
||||||
nextLine = nextR + 1; |
|
||||||
} else { |
|
||||||
lineEnd = nextN; |
|
||||||
if(nextR == (nextN + 1)) |
|
||||||
nextLine = nextR + 1; |
|
||||||
else |
|
||||||
nextLine = nextN + 1; |
|
||||||
} |
|
||||||
} else if(nextN != NULL){ |
|
||||||
lineEnd = nextN; |
|
||||||
nextLine = nextN + 1; |
|
||||||
} else { |
|
||||||
lineEnd = nextR; |
|
||||||
nextLine = nextR + 1; |
|
||||||
} |
|
||||||
|
|
||||||
size_t llen = lineEnd - lineStart; |
|
||||||
char * ldata = (char *)malloc(llen+1); |
|
||||||
if(ldata != NULL){ |
|
||||||
memcpy(ldata, lineStart, llen); |
|
||||||
ldata[llen] = 0; |
|
||||||
ev += "data: "; |
|
||||||
ev += ldata; |
|
||||||
ev += "\r\n"; |
|
||||||
free(ldata); |
|
||||||
} |
|
||||||
lineStart = nextLine; |
|
||||||
if(lineStart == ((char *)message + messageLen)) |
|
||||||
ev += "\r\n"; |
|
||||||
} |
|
||||||
} while(lineStart < ((char *)message + messageLen)); |
|
||||||
} |
|
||||||
|
|
||||||
return ev; |
|
||||||
} |
|
||||||
|
|
||||||
// Message
|
|
||||||
|
|
||||||
AsyncEventSourceMessage::AsyncEventSourceMessage(const char * data, size_t len) |
|
||||||
: _data(nullptr), _len(len), _sent(0), _acked(0) |
|
||||||
{ |
|
||||||
_data = (uint8_t*)malloc(_len+1); |
|
||||||
if(_data == nullptr){ |
|
||||||
_len = 0; |
|
||||||
} else { |
|
||||||
memcpy(_data, data, len); |
|
||||||
_data[_len] = 0; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
AsyncEventSourceMessage::~AsyncEventSourceMessage() { |
|
||||||
if(_data != NULL) |
|
||||||
free(_data); |
|
||||||
} |
|
||||||
|
|
||||||
size_t AsyncEventSourceMessage::ack(size_t len, uint32_t time) { |
|
||||||
(void)time; |
|
||||||
// If the whole message is now acked...
|
|
||||||
if(_acked + len > _len){ |
|
||||||
// Return the number of extra bytes acked (they will be carried on to the next message)
|
|
||||||
const size_t extra = _acked + len - _len; |
|
||||||
_acked = _len; |
|
||||||
return extra; |
|
||||||
} |
|
||||||
// Return that no extra bytes left.
|
|
||||||
_acked += len; |
|
||||||
return 0; |
|
||||||
} |
|
||||||
|
|
||||||
size_t AsyncEventSourceMessage::send(AsyncClient *client) { |
|
||||||
const size_t len = _len - _sent; |
|
||||||
if(client->space() < len){ |
|
||||||
return 0; |
|
||||||
} |
|
||||||
size_t sent = client->add((const char *)_data, len); |
|
||||||
if(client->canSend()) |
|
||||||
client->send(); |
|
||||||
_sent += sent; |
|
||||||
return sent;
|
|
||||||
} |
|
||||||
|
|
||||||
// Client
|
|
||||||
|
|
||||||
AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server) |
|
||||||
: _messageQueue(LinkedList<AsyncEventSourceMessage *>([](AsyncEventSourceMessage *m){ delete m; })) |
|
||||||
{ |
|
||||||
_client = request->client(); |
|
||||||
_server = server; |
|
||||||
_lastId = 0; |
|
||||||
if(request->hasHeader("Last-Event-ID")) |
|
||||||
_lastId = atoi(request->getHeader("Last-Event-ID")->value().c_str()); |
|
||||||
|
|
||||||
_client->setRxTimeout(0); |
|
||||||
_client->onError(NULL, NULL); |
|
||||||
_client->onAck([](void *r, AsyncClient* c, size_t len, uint32_t time){ (void)c; ((AsyncEventSourceClient*)(r))->_onAck(len, time); }, this); |
|
||||||
_client->onPoll([](void *r, AsyncClient* c){ (void)c; ((AsyncEventSourceClient*)(r))->_onPoll(); }, this); |
|
||||||
_client->onData(NULL, NULL); |
|
||||||
_client->onTimeout([this](void *r, AsyncClient* c __attribute__((unused)), uint32_t time){ ((AsyncEventSourceClient*)(r))->_onTimeout(time); }, this); |
|
||||||
_client->onDisconnect([this](void *r, AsyncClient* c){ ((AsyncEventSourceClient*)(r))->_onDisconnect(); delete c; }, this); |
|
||||||
|
|
||||||
_server->_addClient(this); |
|
||||||
delete request; |
|
||||||
} |
|
||||||
|
|
||||||
AsyncEventSourceClient::~AsyncEventSourceClient(){ |
|
||||||
_messageQueue.free(); |
|
||||||
close(); |
|
||||||
} |
|
||||||
|
|
||||||
void AsyncEventSourceClient::_queueMessage(AsyncEventSourceMessage *dataMessage){ |
|
||||||
if(dataMessage == NULL) |
|
||||||
return; |
|
||||||
if(!connected()){ |
|
||||||
delete dataMessage; |
|
||||||
return; |
|
||||||
} |
|
||||||
if(_messageQueue.length() >= SSE_MAX_QUEUED_MESSAGES){ |
|
||||||
ets_printf("ERROR: Too many messages queued\n"); |
|
||||||
delete dataMessage; |
|
||||||
} else { |
|
||||||
_messageQueue.add(dataMessage); |
|
||||||
} |
|
||||||
if(_client->canSend()) |
|
||||||
_runQueue(); |
|
||||||
} |
|
||||||
|
|
||||||
void AsyncEventSourceClient::_onAck(size_t len, uint32_t time){ |
|
||||||
while(len && !_messageQueue.isEmpty()){ |
|
||||||
len = _messageQueue.front()->ack(len, time); |
|
||||||
if(_messageQueue.front()->finished()) |
|
||||||
_messageQueue.remove(_messageQueue.front()); |
|
||||||
} |
|
||||||
|
|
||||||
_runQueue(); |
|
||||||
} |
|
||||||
|
|
||||||
void AsyncEventSourceClient::_onPoll(){ |
|
||||||
if(!_messageQueue.isEmpty()){ |
|
||||||
_runQueue(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
void AsyncEventSourceClient::_onTimeout(uint32_t time __attribute__((unused))){ |
|
||||||
_client->close(true); |
|
||||||
} |
|
||||||
|
|
||||||
void AsyncEventSourceClient::_onDisconnect(){ |
|
||||||
_client = NULL; |
|
||||||
_server->_handleDisconnect(this); |
|
||||||
} |
|
||||||
|
|
||||||
void AsyncEventSourceClient::close(){ |
|
||||||
if(_client != NULL) |
|
||||||
_client->close(); |
|
||||||
} |
|
||||||
|
|
||||||
void AsyncEventSourceClient::write(const char * message, size_t len){ |
|
||||||
_queueMessage(new AsyncEventSourceMessage(message, len)); |
|
||||||
} |
|
||||||
|
|
||||||
void AsyncEventSourceClient::send(const char *message, const char *event, uint32_t id, uint32_t reconnect){ |
|
||||||
String ev = generateEventMessage(message, event, id, reconnect); |
|
||||||
_queueMessage(new AsyncEventSourceMessage(ev.c_str(), ev.length())); |
|
||||||
} |
|
||||||
|
|
||||||
void AsyncEventSourceClient::_runQueue(){ |
|
||||||
while(!_messageQueue.isEmpty() && _messageQueue.front()->finished()){ |
|
||||||
_messageQueue.remove(_messageQueue.front()); |
|
||||||
} |
|
||||||
|
|
||||||
for(auto i = _messageQueue.begin(); i != _messageQueue.end(); ++i) |
|
||||||
{ |
|
||||||
if(!(*i)->sent()) |
|
||||||
(*i)->send(_client); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
// Handler
|
|
||||||
|
|
||||||
AsyncEventSource::AsyncEventSource(const String& url) |
|
||||||
: _url(url) |
|
||||||
, _clients(LinkedList<AsyncEventSourceClient *>([](AsyncEventSourceClient *c){ delete c; })) |
|
||||||
, _connectcb(NULL) |
|
||||||
{} |
|
||||||
|
|
||||||
AsyncEventSource::~AsyncEventSource(){ |
|
||||||
close(); |
|
||||||
} |
|
||||||
|
|
||||||
void AsyncEventSource::onConnect(ArEventHandlerFunction cb){ |
|
||||||
_connectcb = cb; |
|
||||||
} |
|
||||||
|
|
||||||
void AsyncEventSource::_addClient(AsyncEventSourceClient * client){ |
|
||||||
/*char * temp = (char *)malloc(2054);
|
|
||||||
if(temp != NULL){ |
|
||||||
memset(temp+1,' ',2048); |
|
||||||
temp[0] = ':'; |
|
||||||
temp[2049] = '\r'; |
|
||||||
temp[2050] = '\n'; |
|
||||||
temp[2051] = '\r'; |
|
||||||
temp[2052] = '\n'; |
|
||||||
temp[2053] = 0; |
|
||||||
client->write((const char *)temp, 2053); |
|
||||||
free(temp); |
|
||||||
}*/ |
|
||||||
|
|
||||||
_clients.add(client); |
|
||||||
if(_connectcb) |
|
||||||
_connectcb(client); |
|
||||||
} |
|
||||||
|
|
||||||
void AsyncEventSource::_handleDisconnect(AsyncEventSourceClient * client){ |
|
||||||
_clients.remove(client); |
|
||||||
} |
|
||||||
|
|
||||||
void AsyncEventSource::close(){ |
|
||||||
for(const auto &c: _clients){ |
|
||||||
if(c->connected()) |
|
||||||
c->close(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// pmb fix
|
|
||||||
size_t AsyncEventSource::avgPacketsWaiting() const { |
|
||||||
if(_clients.isEmpty()) |
|
||||||
return 0; |
|
||||||
|
|
||||||
size_t aql=0; |
|
||||||
uint32_t nConnectedClients=0; |
|
||||||
|
|
||||||
for(const auto &c: _clients){ |
|
||||||
if(c->connected()) { |
|
||||||
aql+=c->packetsWaiting(); |
|
||||||
++nConnectedClients; |
|
||||||
} |
|
||||||
} |
|
||||||
// return aql / nConnectedClients;
|
|
||||||
return ((aql) + (nConnectedClients/2))/(nConnectedClients); // round up
|
|
||||||
} |
|
||||||
|
|
||||||
void AsyncEventSource::send(const char *message, const char *event, uint32_t id, uint32_t reconnect){ |
|
||||||
|
|
||||||
|
|
||||||
String ev = generateEventMessage(message, event, id, reconnect); |
|
||||||
for(const auto &c: _clients){ |
|
||||||
if(c->connected()) { |
|
||||||
c->write(ev.c_str(), ev.length()); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
size_t AsyncEventSource::count() const { |
|
||||||
return _clients.count_if([](AsyncEventSourceClient *c){ |
|
||||||
return c->connected(); |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
bool AsyncEventSource::canHandle(AsyncWebServerRequest *request){ |
|
||||||
if(request->method() != HTTP_GET || !request->url().equals(_url)) { |
|
||||||
return false; |
|
||||||
} |
|
||||||
request->addInterestingHeader("Last-Event-ID"); |
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
void AsyncEventSource::handleRequest(AsyncWebServerRequest *request){ |
|
||||||
if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str())) |
|
||||||
return request->requestAuthentication(); |
|
||||||
request->send(new AsyncEventSourceResponse(this)); |
|
||||||
} |
|
||||||
|
|
||||||
// Response
|
|
||||||
|
|
||||||
AsyncEventSourceResponse::AsyncEventSourceResponse(AsyncEventSource *server){ |
|
||||||
_server = server; |
|
||||||
_code = 200; |
|
||||||
_contentType = "text/event-stream"; |
|
||||||
_sendContentLength = false; |
|
||||||
addHeader("Cache-Control", "no-cache"); |
|
||||||
addHeader("Connection","keep-alive"); |
|
||||||
} |
|
||||||
|
|
||||||
void AsyncEventSourceResponse::_respond(AsyncWebServerRequest *request){ |
|
||||||
String out = _assembleHead(request->version()); |
|
||||||
request->client()->write(out.c_str(), _headLength); |
|
||||||
_state = RESPONSE_WAIT_ACK; |
|
||||||
} |
|
||||||
|
|
||||||
size_t AsyncEventSourceResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time __attribute__((unused))){ |
|
||||||
if(len){ |
|
||||||
new AsyncEventSourceClient(request, _server); |
|
||||||
} |
|
||||||
return 0; |
|
||||||
} |
|
||||||
|
|
@ -1,133 +0,0 @@ |
|||||||
/*
|
|
||||||
Asynchronous WebServer library for Espressif MCUs |
|
||||||
|
|
||||||
Copyright (c) 2016 Hristo Gochkov. All rights reserved. |
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or |
|
||||||
modify it under the terms of the GNU Lesser General Public |
|
||||||
License as published by the Free Software Foundation; either |
|
||||||
version 2.1 of the License, or (at your option) any later version. |
|
||||||
|
|
||||||
This library is distributed in the hope that it will be useful, |
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
||||||
Lesser General Public License for more details. |
|
||||||
|
|
||||||
You should have received a copy of the GNU Lesser General Public |
|
||||||
License along with this library; if not, write to the Free Software |
|
||||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
|
||||||
*/ |
|
||||||
#ifndef ASYNCEVENTSOURCE_H_ |
|
||||||
#define ASYNCEVENTSOURCE_H_ |
|
||||||
|
|
||||||
#include <Arduino.h> |
|
||||||
#ifdef ESP32 |
|
||||||
#include <AsyncTCP.h> |
|
||||||
#define SSE_MAX_QUEUED_MESSAGES 32 |
|
||||||
#else |
|
||||||
#include <ESPAsyncTCP.h> |
|
||||||
#define SSE_MAX_QUEUED_MESSAGES 8 |
|
||||||
#endif |
|
||||||
#include <ESPAsyncWebServer.h> |
|
||||||
|
|
||||||
#include "AsyncWebSynchronization.h" |
|
||||||
|
|
||||||
#ifdef ESP8266 |
|
||||||
#include <Hash.h> |
|
||||||
#ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library
|
|
||||||
#include <../src/Hash.h> |
|
||||||
#endif |
|
||||||
#endif |
|
||||||
|
|
||||||
#ifdef ESP32 |
|
||||||
#define DEFAULT_MAX_SSE_CLIENTS 8 |
|
||||||
#else |
|
||||||
#define DEFAULT_MAX_SSE_CLIENTS 4 |
|
||||||
#endif |
|
||||||
|
|
||||||
class AsyncEventSource; |
|
||||||
class AsyncEventSourceResponse; |
|
||||||
class AsyncEventSourceClient; |
|
||||||
typedef std::function<void(AsyncEventSourceClient *client)> ArEventHandlerFunction; |
|
||||||
|
|
||||||
class AsyncEventSourceMessage { |
|
||||||
private: |
|
||||||
uint8_t * _data;
|
|
||||||
size_t _len; |
|
||||||
size_t _sent; |
|
||||||
//size_t _ack;
|
|
||||||
size_t _acked;
|
|
||||||
public: |
|
||||||
AsyncEventSourceMessage(const char * data, size_t len); |
|
||||||
~AsyncEventSourceMessage(); |
|
||||||
size_t ack(size_t len, uint32_t time __attribute__((unused))); |
|
||||||
size_t send(AsyncClient *client); |
|
||||||
bool finished(){ return _acked == _len; } |
|
||||||
bool sent() { return _sent == _len; } |
|
||||||
}; |
|
||||||
|
|
||||||
class AsyncEventSourceClient { |
|
||||||
private: |
|
||||||
AsyncClient *_client; |
|
||||||
AsyncEventSource *_server; |
|
||||||
uint32_t _lastId; |
|
||||||
LinkedList<AsyncEventSourceMessage *> _messageQueue; |
|
||||||
void _queueMessage(AsyncEventSourceMessage *dataMessage); |
|
||||||
void _runQueue(); |
|
||||||
|
|
||||||
public: |
|
||||||
|
|
||||||
AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server); |
|
||||||
~AsyncEventSourceClient(); |
|
||||||
|
|
||||||
AsyncClient* client(){ return _client; } |
|
||||||
void close(); |
|
||||||
void write(const char * message, size_t len); |
|
||||||
void send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0); |
|
||||||
bool connected() const { return (_client != NULL) && _client->connected(); } |
|
||||||
uint32_t lastId() const { return _lastId; } |
|
||||||
size_t packetsWaiting() const { return _messageQueue.length(); } |
|
||||||
|
|
||||||
//system callbacks (do not call)
|
|
||||||
void _onAck(size_t len, uint32_t time); |
|
||||||
void _onPoll();
|
|
||||||
void _onTimeout(uint32_t time); |
|
||||||
void _onDisconnect(); |
|
||||||
}; |
|
||||||
|
|
||||||
class AsyncEventSource: public AsyncWebHandler { |
|
||||||
private: |
|
||||||
String _url; |
|
||||||
LinkedList<AsyncEventSourceClient *> _clients; |
|
||||||
ArEventHandlerFunction _connectcb; |
|
||||||
public: |
|
||||||
AsyncEventSource(const String& url); |
|
||||||
~AsyncEventSource(); |
|
||||||
|
|
||||||
const char * url() const { return _url.c_str(); } |
|
||||||
void close(); |
|
||||||
void onConnect(ArEventHandlerFunction cb); |
|
||||||
void send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0); |
|
||||||
size_t count() const; //number clinets connected
|
|
||||||
size_t avgPacketsWaiting() const; |
|
||||||
|
|
||||||
//system callbacks (do not call)
|
|
||||||
void _addClient(AsyncEventSourceClient * client); |
|
||||||
void _handleDisconnect(AsyncEventSourceClient * client); |
|
||||||
virtual bool canHandle(AsyncWebServerRequest *request) override final; |
|
||||||
virtual void handleRequest(AsyncWebServerRequest *request) override final; |
|
||||||
}; |
|
||||||
|
|
||||||
class AsyncEventSourceResponse: public AsyncWebServerResponse { |
|
||||||
private: |
|
||||||
String _content; |
|
||||||
AsyncEventSource *_server; |
|
||||||
public: |
|
||||||
AsyncEventSourceResponse(AsyncEventSource *server); |
|
||||||
void _respond(AsyncWebServerRequest *request); |
|
||||||
size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); |
|
||||||
bool _sourceValid() const { return true; } |
|
||||||
}; |
|
||||||
|
|
||||||
|
|
||||||
#endif /* ASYNCEVENTSOURCE_H_ */ |
|
@ -1,254 +0,0 @@ |
|||||||
// AsyncJson.h
|
|
||||||
/*
|
|
||||||
Async Response to use with ArduinoJson and AsyncWebServer |
|
||||||
Written by Andrew Melvin (SticilFace) with help from me-no-dev and BBlanchon. |
|
||||||
|
|
||||||
Example of callback in use |
|
||||||
|
|
||||||
server.on("/json", HTTP_ANY, [](AsyncWebServerRequest * request) { |
|
||||||
|
|
||||||
AsyncJsonResponse * response = new AsyncJsonResponse(); |
|
||||||
JsonObject& root = response->getRoot(); |
|
||||||
root["key1"] = "key number one"; |
|
||||||
JsonObject& nested = root.createNestedObject("nested"); |
|
||||||
nested["key1"] = "key number one"; |
|
||||||
|
|
||||||
response->setLength(); |
|
||||||
request->send(response); |
|
||||||
}); |
|
||||||
|
|
||||||
-------------------- |
|
||||||
|
|
||||||
Async Request to use with ArduinoJson and AsyncWebServer |
|
||||||
Written by Arsène von Wyss (avonwyss) |
|
||||||
|
|
||||||
Example |
|
||||||
|
|
||||||
AsyncCallbackJsonWebHandler* handler = new AsyncCallbackJsonWebHandler("/rest/endpoint"); |
|
||||||
handler->onRequest([](AsyncWebServerRequest *request, JsonVariant &json) { |
|
||||||
JsonObject& jsonObj = json.as<JsonObject>(); |
|
||||||
// ...
|
|
||||||
}); |
|
||||||
server.addHandler(handler); |
|
||||||
|
|
||||||
*/ |
|
||||||
#ifndef ASYNC_JSON_H_ |
|
||||||
#define ASYNC_JSON_H_ |
|
||||||
#include <ArduinoJson.h> |
|
||||||
#include <ESPAsyncWebServer.h> |
|
||||||
#include <Print.h> |
|
||||||
|
|
||||||
#if ARDUINOJSON_VERSION_MAJOR == 5 |
|
||||||
#define ARDUINOJSON_5_COMPATIBILITY |
|
||||||
#else |
|
||||||
#ifndef DYNAMIC_JSON_DOCUMENT_SIZE |
|
||||||
#define DYNAMIC_JSON_DOCUMENT_SIZE 1024 |
|
||||||
#endif |
|
||||||
#endif |
|
||||||
|
|
||||||
constexpr const char* JSON_MIMETYPE = "application/json"; |
|
||||||
|
|
||||||
/*
|
|
||||||
* Json Response |
|
||||||
* */ |
|
||||||
|
|
||||||
class ChunkPrint : public Print { |
|
||||||
private: |
|
||||||
uint8_t* _destination; |
|
||||||
size_t _to_skip; |
|
||||||
size_t _to_write; |
|
||||||
size_t _pos; |
|
||||||
public: |
|
||||||
ChunkPrint(uint8_t* destination, size_t from, size_t len) |
|
||||||
: _destination(destination), _to_skip(from), _to_write(len), _pos{0} {} |
|
||||||
virtual ~ChunkPrint(){} |
|
||||||
size_t write(uint8_t c){ |
|
||||||
if (_to_skip > 0) { |
|
||||||
_to_skip--; |
|
||||||
return 1; |
|
||||||
} else if (_to_write > 0) { |
|
||||||
_to_write--; |
|
||||||
_destination[_pos++] = c; |
|
||||||
return 1; |
|
||||||
} |
|
||||||
return 0; |
|
||||||
} |
|
||||||
size_t write(const uint8_t *buffer, size_t size) |
|
||||||
{ |
|
||||||
return this->Print::write(buffer, size); |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
class AsyncJsonResponse: public AsyncAbstractResponse { |
|
||||||
protected: |
|
||||||
|
|
||||||
#ifdef ARDUINOJSON_5_COMPATIBILITY |
|
||||||
DynamicJsonBuffer _jsonBuffer; |
|
||||||
#else |
|
||||||
DynamicJsonDocument _jsonBuffer; |
|
||||||
#endif |
|
||||||
|
|
||||||
JsonVariant _root; |
|
||||||
bool _isValid; |
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
#ifdef ARDUINOJSON_5_COMPATIBILITY |
|
||||||
AsyncJsonResponse(bool isArray=false): _isValid{false} { |
|
||||||
_code = 200; |
|
||||||
_contentType = JSON_MIMETYPE; |
|
||||||
if(isArray) |
|
||||||
_root = _jsonBuffer.createArray(); |
|
||||||
else |
|
||||||
_root = _jsonBuffer.createObject(); |
|
||||||
} |
|
||||||
#else |
|
||||||
AsyncJsonResponse(bool isArray=false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE) : _jsonBuffer(maxJsonBufferSize), _isValid{false} { |
|
||||||
_code = 200; |
|
||||||
_contentType = JSON_MIMETYPE; |
|
||||||
if(isArray) |
|
||||||
_root = _jsonBuffer.createNestedArray(); |
|
||||||
else |
|
||||||
_root = _jsonBuffer.createNestedObject(); |
|
||||||
} |
|
||||||
#endif |
|
||||||
|
|
||||||
~AsyncJsonResponse() {} |
|
||||||
JsonVariant & getRoot() { return _root; } |
|
||||||
bool _sourceValid() const { return _isValid; } |
|
||||||
size_t setLength() { |
|
||||||
|
|
||||||
#ifdef ARDUINOJSON_5_COMPATIBILITY |
|
||||||
_contentLength = _root.measureLength(); |
|
||||||
#else |
|
||||||
_contentLength = measureJson(_root); |
|
||||||
#endif |
|
||||||
|
|
||||||
if (_contentLength) { _isValid = true; } |
|
||||||
return _contentLength; |
|
||||||
} |
|
||||||
|
|
||||||
size_t getSize() { return _jsonBuffer.size(); } |
|
||||||
|
|
||||||
size_t _fillBuffer(uint8_t *data, size_t len){ |
|
||||||
ChunkPrint dest(data, _sentLength, len); |
|
||||||
|
|
||||||
#ifdef ARDUINOJSON_5_COMPATIBILITY |
|
||||||
_root.printTo( dest ) ; |
|
||||||
#else |
|
||||||
serializeJson(_root, dest); |
|
||||||
#endif |
|
||||||
return len; |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
class PrettyAsyncJsonResponse: public AsyncJsonResponse {
|
|
||||||
public: |
|
||||||
#ifdef ARDUINOJSON_5_COMPATIBILITY |
|
||||||
PrettyAsyncJsonResponse (bool isArray=false) : AsyncJsonResponse{isArray} {} |
|
||||||
#else |
|
||||||
PrettyAsyncJsonResponse (bool isArray=false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE) : AsyncJsonResponse{isArray, maxJsonBufferSize} {} |
|
||||||
#endif |
|
||||||
size_t setLength () { |
|
||||||
#ifdef ARDUINOJSON_5_COMPATIBILITY |
|
||||||
_contentLength = _root.measurePrettyLength (); |
|
||||||
#else |
|
||||||
_contentLength = measureJsonPretty(_root); |
|
||||||
#endif |
|
||||||
if (_contentLength) {_isValid = true;} |
|
||||||
return _contentLength; |
|
||||||
} |
|
||||||
size_t _fillBuffer (uint8_t *data, size_t len) { |
|
||||||
ChunkPrint dest (data, _sentLength, len); |
|
||||||
#ifdef ARDUINOJSON_5_COMPATIBILITY |
|
||||||
_root.prettyPrintTo (dest); |
|
||||||
#else |
|
||||||
serializeJsonPretty(_root, dest); |
|
||||||
#endif |
|
||||||
return len; |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
typedef std::function<void(AsyncWebServerRequest *request, JsonVariant &json)> ArJsonRequestHandlerFunction; |
|
||||||
|
|
||||||
class AsyncCallbackJsonWebHandler: public AsyncWebHandler { |
|
||||||
private: |
|
||||||
protected: |
|
||||||
const String _uri; |
|
||||||
WebRequestMethodComposite _method; |
|
||||||
ArJsonRequestHandlerFunction _onRequest; |
|
||||||
size_t _contentLength; |
|
||||||
#ifndef ARDUINOJSON_5_COMPATIBILITY |
|
||||||
const size_t maxJsonBufferSize; |
|
||||||
#endif |
|
||||||
size_t _maxContentLength; |
|
||||||
public: |
|
||||||
#ifdef ARDUINOJSON_5_COMPATIBILITY |
|
||||||
AsyncCallbackJsonWebHandler(const String& uri, ArJsonRequestHandlerFunction onRequest)
|
|
||||||
: _uri(uri), _method(HTTP_POST|HTTP_PUT|HTTP_PATCH), _onRequest(onRequest), _maxContentLength(16384) {} |
|
||||||
#else |
|
||||||
AsyncCallbackJsonWebHandler(const String& uri, ArJsonRequestHandlerFunction onRequest, size_t maxJsonBufferSize=DYNAMIC_JSON_DOCUMENT_SIZE)
|
|
||||||
: _uri(uri), _method(HTTP_POST|HTTP_PUT|HTTP_PATCH), _onRequest(onRequest), maxJsonBufferSize(maxJsonBufferSize), _maxContentLength(16384) {} |
|
||||||
#endif |
|
||||||
|
|
||||||
void setMethod(WebRequestMethodComposite method){ _method = method; } |
|
||||||
void setMaxContentLength(int maxContentLength){ _maxContentLength = maxContentLength; } |
|
||||||
void onRequest(ArJsonRequestHandlerFunction fn){ _onRequest = fn; } |
|
||||||
|
|
||||||
virtual bool canHandle(AsyncWebServerRequest *request) override final{ |
|
||||||
if(!_onRequest) |
|
||||||
return false; |
|
||||||
|
|
||||||
if(!(_method & request->method())) |
|
||||||
return false; |
|
||||||
|
|
||||||
if(_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri+"/"))) |
|
||||||
return false; |
|
||||||
|
|
||||||
if ( !request->contentType().equalsIgnoreCase(JSON_MIMETYPE) ) |
|
||||||
return false; |
|
||||||
|
|
||||||
request->addInterestingHeader("ANY"); |
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
virtual void handleRequest(AsyncWebServerRequest *request) override final { |
|
||||||
if(_onRequest) { |
|
||||||
if (request->_tempObject != NULL) { |
|
||||||
|
|
||||||
#ifdef ARDUINOJSON_5_COMPATIBILITY |
|
||||||
DynamicJsonBuffer jsonBuffer; |
|
||||||
JsonVariant json = jsonBuffer.parse((uint8_t*)(request->_tempObject)); |
|
||||||
if (json.success()) { |
|
||||||
#else |
|
||||||
DynamicJsonDocument jsonBuffer(this->maxJsonBufferSize); |
|
||||||
DeserializationError error = deserializeJson(jsonBuffer, (uint8_t*)(request->_tempObject)); |
|
||||||
if(!error) { |
|
||||||
JsonVariant json = jsonBuffer.as<JsonVariant>(); |
|
||||||
#endif |
|
||||||
|
|
||||||
_onRequest(request, json); |
|
||||||
return; |
|
||||||
} |
|
||||||
} |
|
||||||
request->send(_contentLength > _maxContentLength ? 413 : 400); |
|
||||||
} else { |
|
||||||
request->send(500); |
|
||||||
} |
|
||||||
} |
|
||||||
virtual void handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) override final { |
|
||||||
} |
|
||||||
virtual void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override final { |
|
||||||
if (_onRequest) { |
|
||||||
_contentLength = total; |
|
||||||
if (total > 0 && request->_tempObject == NULL && total < _maxContentLength) { |
|
||||||
request->_tempObject = malloc(total); |
|
||||||
} |
|
||||||
if (request->_tempObject != NULL) { |
|
||||||
memcpy((uint8_t*)(request->_tempObject) + index, data, len); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
virtual bool isRequestHandlerTrivial() override final {return _onRequest ? false : true;} |
|
||||||
}; |
|
||||||
#endif |
|
File diff suppressed because it is too large
Load Diff
@ -1,350 +0,0 @@ |
|||||||
/*
|
|
||||||
Asynchronous WebServer library for Espressif MCUs |
|
||||||
|
|
||||||
Copyright (c) 2016 Hristo Gochkov. All rights reserved. |
|
||||||
This file is part of the esp8266 core for Arduino environment. |
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or |
|
||||||
modify it under the terms of the GNU Lesser General Public |
|
||||||
License as published by the Free Software Foundation; either |
|
||||||
version 2.1 of the License, or (at your option) any later version. |
|
||||||
|
|
||||||
This library is distributed in the hope that it will be useful, |
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
||||||
Lesser General Public License for more details. |
|
||||||
|
|
||||||
You should have received a copy of the GNU Lesser General Public |
|
||||||
License along with this library; if not, write to the Free Software |
|
||||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
|
||||||
*/ |
|
||||||
#ifndef ASYNCWEBSOCKET_H_ |
|
||||||
#define ASYNCWEBSOCKET_H_ |
|
||||||
|
|
||||||
#include <Arduino.h> |
|
||||||
#ifdef ESP32 |
|
||||||
#include <AsyncTCP.h> |
|
||||||
#define WS_MAX_QUEUED_MESSAGES 32 |
|
||||||
#else |
|
||||||
#include <ESPAsyncTCP.h> |
|
||||||
#define WS_MAX_QUEUED_MESSAGES 8 |
|
||||||
#endif |
|
||||||
#include <ESPAsyncWebServer.h> |
|
||||||
|
|
||||||
#include "AsyncWebSynchronization.h" |
|
||||||
|
|
||||||
#ifdef ESP8266 |
|
||||||
#include <Hash.h> |
|
||||||
#ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library
|
|
||||||
#include <../src/Hash.h> |
|
||||||
#endif |
|
||||||
#endif |
|
||||||
|
|
||||||
#ifdef ESP32 |
|
||||||
#define DEFAULT_MAX_WS_CLIENTS 8 |
|
||||||
#else |
|
||||||
#define DEFAULT_MAX_WS_CLIENTS 4 |
|
||||||
#endif |
|
||||||
|
|
||||||
class AsyncWebSocket; |
|
||||||
class AsyncWebSocketResponse; |
|
||||||
class AsyncWebSocketClient; |
|
||||||
class AsyncWebSocketControl; |
|
||||||
|
|
||||||
typedef struct { |
|
||||||
/** Message type as defined by enum AwsFrameType.
|
|
||||||
* Note: Applications will only see WS_TEXT and WS_BINARY. |
|
||||||
* All other types are handled by the library. */ |
|
||||||
uint8_t message_opcode; |
|
||||||
/** Frame number of a fragmented message. */ |
|
||||||
uint32_t num; |
|
||||||
/** Is this the last frame in a fragmented message ?*/ |
|
||||||
uint8_t final; |
|
||||||
/** Is this frame masked? */ |
|
||||||
uint8_t masked; |
|
||||||
/** Message type as defined by enum AwsFrameType.
|
|
||||||
* This value is the same as message_opcode for non-fragmented |
|
||||||
* messages, but may also be WS_CONTINUATION in a fragmented message. */ |
|
||||||
uint8_t opcode; |
|
||||||
/** Length of the current frame.
|
|
||||||
* This equals the total length of the message if num == 0 && final == true */ |
|
||||||
uint64_t len; |
|
||||||
/** Mask key */ |
|
||||||
uint8_t mask[4]; |
|
||||||
/** Offset of the data inside the current frame. */ |
|
||||||
uint64_t index; |
|
||||||
} AwsFrameInfo; |
|
||||||
|
|
||||||
typedef enum { WS_DISCONNECTED, WS_CONNECTED, WS_DISCONNECTING } AwsClientStatus; |
|
||||||
typedef enum { WS_CONTINUATION, WS_TEXT, WS_BINARY, WS_DISCONNECT = 0x08, WS_PING, WS_PONG } AwsFrameType; |
|
||||||
typedef enum { WS_MSG_SENDING, WS_MSG_SENT, WS_MSG_ERROR } AwsMessageStatus; |
|
||||||
typedef enum { WS_EVT_CONNECT, WS_EVT_DISCONNECT, WS_EVT_PONG, WS_EVT_ERROR, WS_EVT_DATA } AwsEventType; |
|
||||||
|
|
||||||
class AsyncWebSocketMessageBuffer { |
|
||||||
private: |
|
||||||
uint8_t * _data; |
|
||||||
size_t _len; |
|
||||||
bool _lock;
|
|
||||||
uint32_t _count;
|
|
||||||
|
|
||||||
public: |
|
||||||
AsyncWebSocketMessageBuffer(); |
|
||||||
AsyncWebSocketMessageBuffer(size_t size); |
|
||||||
AsyncWebSocketMessageBuffer(uint8_t * data, size_t size);
|
|
||||||
AsyncWebSocketMessageBuffer(const AsyncWebSocketMessageBuffer &);
|
|
||||||
AsyncWebSocketMessageBuffer(AsyncWebSocketMessageBuffer &&);
|
|
||||||
~AsyncWebSocketMessageBuffer();
|
|
||||||
void operator ++(int i) { (void)i; _count++; } |
|
||||||
void operator --(int i) { (void)i; if (_count > 0) { _count--; } ; } |
|
||||||
bool reserve(size_t size); |
|
||||||
void lock() { _lock = true; } |
|
||||||
void unlock() { _lock = false; } |
|
||||||
uint8_t * get() { return _data; } |
|
||||||
size_t length() { return _len; } |
|
||||||
uint32_t count() { return _count; } |
|
||||||
bool canDelete() { return (!_count && !_lock); }
|
|
||||||
|
|
||||||
friend AsyncWebSocket;
|
|
||||||
|
|
||||||
}; |
|
||||||
|
|
||||||
class AsyncWebSocketMessage { |
|
||||||
protected: |
|
||||||
uint8_t _opcode; |
|
||||||
bool _mask; |
|
||||||
AwsMessageStatus _status; |
|
||||||
public: |
|
||||||
AsyncWebSocketMessage():_opcode(WS_TEXT),_mask(false),_status(WS_MSG_ERROR){} |
|
||||||
virtual ~AsyncWebSocketMessage(){} |
|
||||||
virtual void ack(size_t len __attribute__((unused)), uint32_t time __attribute__((unused))){} |
|
||||||
virtual size_t send(AsyncClient *client __attribute__((unused))){ return 0; } |
|
||||||
virtual bool finished(){ return _status != WS_MSG_SENDING; } |
|
||||||
virtual bool betweenFrames() const { return false; } |
|
||||||
}; |
|
||||||
|
|
||||||
class AsyncWebSocketBasicMessage: public AsyncWebSocketMessage { |
|
||||||
private: |
|
||||||
size_t _len; |
|
||||||
size_t _sent; |
|
||||||
size_t _ack; |
|
||||||
size_t _acked; |
|
||||||
uint8_t * _data; |
|
||||||
public: |
|
||||||
AsyncWebSocketBasicMessage(const char * data, size_t len, uint8_t opcode=WS_TEXT, bool mask=false); |
|
||||||
AsyncWebSocketBasicMessage(uint8_t opcode=WS_TEXT, bool mask=false); |
|
||||||
virtual ~AsyncWebSocketBasicMessage() override; |
|
||||||
virtual bool betweenFrames() const override { return _acked == _ack; } |
|
||||||
virtual void ack(size_t len, uint32_t time) override ; |
|
||||||
virtual size_t send(AsyncClient *client) override ; |
|
||||||
}; |
|
||||||
|
|
||||||
class AsyncWebSocketMultiMessage: public AsyncWebSocketMessage { |
|
||||||
private: |
|
||||||
uint8_t * _data; |
|
||||||
size_t _len; |
|
||||||
size_t _sent; |
|
||||||
size_t _ack; |
|
||||||
size_t _acked; |
|
||||||
AsyncWebSocketMessageBuffer * _WSbuffer;
|
|
||||||
public: |
|
||||||
AsyncWebSocketMultiMessage(AsyncWebSocketMessageBuffer * buffer, uint8_t opcode=WS_TEXT, bool mask=false);
|
|
||||||
virtual ~AsyncWebSocketMultiMessage() override; |
|
||||||
virtual bool betweenFrames() const override { return _acked == _ack; } |
|
||||||
virtual void ack(size_t len, uint32_t time) override ; |
|
||||||
virtual size_t send(AsyncClient *client) override ; |
|
||||||
}; |
|
||||||
|
|
||||||
class AsyncWebSocketClient { |
|
||||||
private: |
|
||||||
AsyncClient *_client; |
|
||||||
AsyncWebSocket *_server; |
|
||||||
uint32_t _clientId; |
|
||||||
AwsClientStatus _status; |
|
||||||
|
|
||||||
LinkedList<AsyncWebSocketControl *> _controlQueue; |
|
||||||
LinkedList<AsyncWebSocketMessage *> _messageQueue; |
|
||||||
|
|
||||||
uint8_t _pstate; |
|
||||||
AwsFrameInfo _pinfo; |
|
||||||
|
|
||||||
uint32_t _lastMessageTime; |
|
||||||
uint32_t _keepAlivePeriod; |
|
||||||
|
|
||||||
void _queueMessage(AsyncWebSocketMessage *dataMessage); |
|
||||||
void _queueControl(AsyncWebSocketControl *controlMessage); |
|
||||||
void _runQueue(); |
|
||||||
|
|
||||||
public: |
|
||||||
void *_tempObject; |
|
||||||
|
|
||||||
AsyncWebSocketClient(AsyncWebServerRequest *request, AsyncWebSocket *server); |
|
||||||
~AsyncWebSocketClient(); |
|
||||||
|
|
||||||
//client id increments for the given server
|
|
||||||
uint32_t id(){ return _clientId; } |
|
||||||
AwsClientStatus status(){ return _status; } |
|
||||||
AsyncClient* client(){ return _client; } |
|
||||||
AsyncWebSocket *server(){ return _server; } |
|
||||||
AwsFrameInfo const &pinfo() const { return _pinfo; } |
|
||||||
|
|
||||||
IPAddress remoteIP(); |
|
||||||
uint16_t remotePort(); |
|
||||||
|
|
||||||
//control frames
|
|
||||||
void close(uint16_t code=0, const char * message=NULL); |
|
||||||
void ping(uint8_t *data=NULL, size_t len=0); |
|
||||||
|
|
||||||
//set auto-ping period in seconds. disabled if zero (default)
|
|
||||||
void keepAlivePeriod(uint16_t seconds){ |
|
||||||
_keepAlivePeriod = seconds * 1000; |
|
||||||
} |
|
||||||
uint16_t keepAlivePeriod(){ |
|
||||||
return (uint16_t)(_keepAlivePeriod / 1000); |
|
||||||
} |
|
||||||
|
|
||||||
//data packets
|
|
||||||
void message(AsyncWebSocketMessage *message){ _queueMessage(message); } |
|
||||||
bool queueIsFull(); |
|
||||||
|
|
||||||
size_t printf(const char *format, ...) __attribute__ ((format (printf, 2, 3))); |
|
||||||
#ifndef ESP32 |
|
||||||
size_t printf_P(PGM_P formatP, ...) __attribute__ ((format (printf, 2, 3))); |
|
||||||
#endif |
|
||||||
void text(const char * message, size_t len); |
|
||||||
void text(const char * message); |
|
||||||
void text(uint8_t * message, size_t len); |
|
||||||
void text(char * message); |
|
||||||
void text(const String &message); |
|
||||||
void text(const __FlashStringHelper *data); |
|
||||||
void text(AsyncWebSocketMessageBuffer *buffer);
|
|
||||||
|
|
||||||
void binary(const char * message, size_t len); |
|
||||||
void binary(const char * message); |
|
||||||
void binary(uint8_t * message, size_t len); |
|
||||||
void binary(char * message); |
|
||||||
void binary(const String &message); |
|
||||||
void binary(const __FlashStringHelper *data, size_t len); |
|
||||||
void binary(AsyncWebSocketMessageBuffer *buffer);
|
|
||||||
|
|
||||||
bool canSend() { return _messageQueue.length() < WS_MAX_QUEUED_MESSAGES; } |
|
||||||
|
|
||||||
//system callbacks (do not call)
|
|
||||||
void _onAck(size_t len, uint32_t time); |
|
||||||
void _onError(int8_t); |
|
||||||
void _onPoll(); |
|
||||||
void _onTimeout(uint32_t time); |
|
||||||
void _onDisconnect(); |
|
||||||
void _onData(void *pbuf, size_t plen); |
|
||||||
}; |
|
||||||
|
|
||||||
typedef std::function<void(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len)> AwsEventHandler; |
|
||||||
|
|
||||||
//WebServer Handler implementation that plays the role of a socket server
|
|
||||||
class AsyncWebSocket: public AsyncWebHandler { |
|
||||||
public: |
|
||||||
typedef LinkedList<AsyncWebSocketClient *> AsyncWebSocketClientLinkedList; |
|
||||||
private: |
|
||||||
String _url; |
|
||||||
AsyncWebSocketClientLinkedList _clients; |
|
||||||
uint32_t _cNextId; |
|
||||||
AwsEventHandler _eventHandler; |
|
||||||
bool _enabled; |
|
||||||
AsyncWebLock _lock; |
|
||||||
|
|
||||||
public: |
|
||||||
AsyncWebSocket(const String& url); |
|
||||||
~AsyncWebSocket(); |
|
||||||
const char * url() const { return _url.c_str(); } |
|
||||||
void enable(bool e){ _enabled = e; } |
|
||||||
bool enabled() const { return _enabled; } |
|
||||||
bool availableForWriteAll(); |
|
||||||
bool availableForWrite(uint32_t id); |
|
||||||
|
|
||||||
size_t count() const; |
|
||||||
AsyncWebSocketClient * client(uint32_t id); |
|
||||||
bool hasClient(uint32_t id){ return client(id) != NULL; } |
|
||||||
|
|
||||||
void close(uint32_t id, uint16_t code=0, const char * message=NULL); |
|
||||||
void closeAll(uint16_t code=0, const char * message=NULL); |
|
||||||
void cleanupClients(uint16_t maxClients = DEFAULT_MAX_WS_CLIENTS); |
|
||||||
|
|
||||||
void ping(uint32_t id, uint8_t *data=NULL, size_t len=0); |
|
||||||
void pingAll(uint8_t *data=NULL, size_t len=0); // done
|
|
||||||
|
|
||||||
void text(uint32_t id, const char * message, size_t len); |
|
||||||
void text(uint32_t id, const char * message); |
|
||||||
void text(uint32_t id, uint8_t * message, size_t len); |
|
||||||
void text(uint32_t id, char * message); |
|
||||||
void text(uint32_t id, const String &message); |
|
||||||
void text(uint32_t id, const __FlashStringHelper *message); |
|
||||||
|
|
||||||
void textAll(const char * message, size_t len); |
|
||||||
void textAll(const char * message); |
|
||||||
void textAll(uint8_t * message, size_t len); |
|
||||||
void textAll(char * message); |
|
||||||
void textAll(const String &message); |
|
||||||
void textAll(const __FlashStringHelper *message); // need to convert
|
|
||||||
void textAll(AsyncWebSocketMessageBuffer * buffer);
|
|
||||||
|
|
||||||
void binary(uint32_t id, const char * message, size_t len); |
|
||||||
void binary(uint32_t id, const char * message); |
|
||||||
void binary(uint32_t id, uint8_t * message, size_t len); |
|
||||||
void binary(uint32_t id, char * message); |
|
||||||
void binary(uint32_t id, const String &message); |
|
||||||
void binary(uint32_t id, const __FlashStringHelper *message, size_t len); |
|
||||||
|
|
||||||
void binaryAll(const char * message, size_t len); |
|
||||||
void binaryAll(const char * message); |
|
||||||
void binaryAll(uint8_t * message, size_t len); |
|
||||||
void binaryAll(char * message); |
|
||||||
void binaryAll(const String &message); |
|
||||||
void binaryAll(const __FlashStringHelper *message, size_t len); |
|
||||||
void binaryAll(AsyncWebSocketMessageBuffer * buffer);
|
|
||||||
|
|
||||||
void message(uint32_t id, AsyncWebSocketMessage *message); |
|
||||||
void messageAll(AsyncWebSocketMultiMessage *message); |
|
||||||
|
|
||||||
size_t printf(uint32_t id, const char *format, ...) __attribute__ ((format (printf, 3, 4))); |
|
||||||
size_t printfAll(const char *format, ...) __attribute__ ((format (printf, 2, 3))); |
|
||||||
#ifndef ESP32 |
|
||||||
size_t printf_P(uint32_t id, PGM_P formatP, ...) __attribute__ ((format (printf, 3, 4))); |
|
||||||
#endif |
|
||||||
size_t printfAll_P(PGM_P formatP, ...) __attribute__ ((format (printf, 2, 3))); |
|
||||||
|
|
||||||
//event listener
|
|
||||||
void onEvent(AwsEventHandler handler){ |
|
||||||
_eventHandler = handler; |
|
||||||
} |
|
||||||
|
|
||||||
//system callbacks (do not call)
|
|
||||||
uint32_t _getNextId(){ return _cNextId++; } |
|
||||||
void _addClient(AsyncWebSocketClient * client); |
|
||||||
void _handleDisconnect(AsyncWebSocketClient * client); |
|
||||||
void _handleEvent(AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len); |
|
||||||
virtual bool canHandle(AsyncWebServerRequest *request) override final; |
|
||||||
virtual void handleRequest(AsyncWebServerRequest *request) override final; |
|
||||||
|
|
||||||
|
|
||||||
// messagebuffer functions/objects.
|
|
||||||
AsyncWebSocketMessageBuffer * makeBuffer(size_t size = 0);
|
|
||||||
AsyncWebSocketMessageBuffer * makeBuffer(uint8_t * data, size_t size);
|
|
||||||
LinkedList<AsyncWebSocketMessageBuffer *> _buffers; |
|
||||||
void _cleanBuffers();
|
|
||||||
|
|
||||||
AsyncWebSocketClientLinkedList getClients() const; |
|
||||||
}; |
|
||||||
|
|
||||||
//WebServer response to authenticate the socket and detach the tcp client from the web server request
|
|
||||||
class AsyncWebSocketResponse: public AsyncWebServerResponse { |
|
||||||
private: |
|
||||||
String _content; |
|
||||||
AsyncWebSocket *_server; |
|
||||||
public: |
|
||||||
AsyncWebSocketResponse(const String& key, AsyncWebSocket *server); |
|
||||||
void _respond(AsyncWebServerRequest *request); |
|
||||||
size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); |
|
||||||
bool _sourceValid() const { return true; } |
|
||||||
}; |
|
||||||
|
|
||||||
|
|
||||||
#endif /* ASYNCWEBSOCKET_H_ */ |
|
@ -1,87 +0,0 @@ |
|||||||
#ifndef ASYNCWEBSYNCHRONIZATION_H_ |
|
||||||
#define ASYNCWEBSYNCHRONIZATION_H_ |
|
||||||
|
|
||||||
// Synchronisation is only available on ESP32, as the ESP8266 isn't using FreeRTOS by default
|
|
||||||
|
|
||||||
#include <ESPAsyncWebServer.h> |
|
||||||
|
|
||||||
#ifdef ESP32 |
|
||||||
|
|
||||||
// This is the ESP32 version of the Sync Lock, using the FreeRTOS Semaphore
|
|
||||||
class AsyncWebLock |
|
||||||
{ |
|
||||||
private: |
|
||||||
SemaphoreHandle_t _lock; |
|
||||||
mutable void *_lockedBy; |
|
||||||
|
|
||||||
public: |
|
||||||
AsyncWebLock() { |
|
||||||
_lock = xSemaphoreCreateBinary(); |
|
||||||
_lockedBy = NULL; |
|
||||||
xSemaphoreGive(_lock); |
|
||||||
} |
|
||||||
|
|
||||||
~AsyncWebLock() { |
|
||||||
vSemaphoreDelete(_lock); |
|
||||||
} |
|
||||||
|
|
||||||
bool lock() const { |
|
||||||
extern void *pxCurrentTCB; |
|
||||||
if (_lockedBy != pxCurrentTCB) { |
|
||||||
xSemaphoreTake(_lock, portMAX_DELAY); |
|
||||||
_lockedBy = pxCurrentTCB; |
|
||||||
return true; |
|
||||||
} |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
void unlock() const { |
|
||||||
_lockedBy = NULL; |
|
||||||
xSemaphoreGive(_lock); |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
#else |
|
||||||
|
|
||||||
// This is the 8266 version of the Sync Lock which is currently unimplemented
|
|
||||||
class AsyncWebLock |
|
||||||
{ |
|
||||||
|
|
||||||
public: |
|
||||||
AsyncWebLock() { |
|
||||||
} |
|
||||||
|
|
||||||
~AsyncWebLock() { |
|
||||||
} |
|
||||||
|
|
||||||
bool lock() const { |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
void unlock() const { |
|
||||||
} |
|
||||||
}; |
|
||||||
#endif |
|
||||||
|
|
||||||
class AsyncWebLockGuard |
|
||||||
{ |
|
||||||
private: |
|
||||||
const AsyncWebLock *_lock; |
|
||||||
|
|
||||||
public: |
|
||||||
AsyncWebLockGuard(const AsyncWebLock &l) { |
|
||||||
if (l.lock()) { |
|
||||||
_lock = &l; |
|
||||||
} else { |
|
||||||
_lock = NULL; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
~AsyncWebLockGuard() { |
|
||||||
if (_lock) { |
|
||||||
_lock->unlock(); |
|
||||||
} |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
#endif // ASYNCWEBSYNCHRONIZATION_H_
|
|
@ -1,471 +0,0 @@ |
|||||||
/*
|
|
||||||
Asynchronous WebServer library for Espressif MCUs |
|
||||||
|
|
||||||
Copyright (c) 2016 Hristo Gochkov. All rights reserved. |
|
||||||
This file is part of the esp8266 core for Arduino environment. |
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or |
|
||||||
modify it under the terms of the GNU Lesser General Public |
|
||||||
License as published by the Free Software Foundation; either |
|
||||||
version 2.1 of the License, or (at your option) any later version. |
|
||||||
|
|
||||||
This library is distributed in the hope that it will be useful, |
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
||||||
Lesser General Public License for more details. |
|
||||||
|
|
||||||
You should have received a copy of the GNU Lesser General Public |
|
||||||
License along with this library; if not, write to the Free Software |
|
||||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
|
||||||
*/ |
|
||||||
#ifndef _ESPAsyncWebServer_H_ |
|
||||||
#define _ESPAsyncWebServer_H_ |
|
||||||
|
|
||||||
#include "Arduino.h" |
|
||||||
|
|
||||||
#include <functional> |
|
||||||
#include "FS.h" |
|
||||||
|
|
||||||
#include "StringArray.h" |
|
||||||
|
|
||||||
#ifdef ESP32 |
|
||||||
#include <WiFi.h> |
|
||||||
#include <AsyncTCP.h> |
|
||||||
#elif defined(ESP8266) |
|
||||||
#include <ESP8266WiFi.h> |
|
||||||
#include <ESPAsyncTCP.h> |
|
||||||
#else |
|
||||||
#error Platform not supported |
|
||||||
#endif |
|
||||||
|
|
||||||
#ifdef ASYNCWEBSERVER_REGEX |
|
||||||
#define ASYNCWEBSERVER_REGEX_ATTRIBUTE |
|
||||||
#else |
|
||||||
#define ASYNCWEBSERVER_REGEX_ATTRIBUTE __attribute__((warning("ASYNCWEBSERVER_REGEX not defined"))) |
|
||||||
#endif |
|
||||||
|
|
||||||
#define DEBUGF(...) //Serial.printf(__VA_ARGS__)
|
|
||||||
|
|
||||||
class AsyncWebServer; |
|
||||||
class AsyncWebServerRequest; |
|
||||||
class AsyncWebServerResponse; |
|
||||||
class AsyncWebHeader; |
|
||||||
class AsyncWebParameter; |
|
||||||
class AsyncWebRewrite; |
|
||||||
class AsyncWebHandler; |
|
||||||
class AsyncStaticWebHandler; |
|
||||||
class AsyncCallbackWebHandler; |
|
||||||
class AsyncResponseStream; |
|
||||||
|
|
||||||
#ifndef WEBSERVER_H |
|
||||||
typedef enum { |
|
||||||
HTTP_GET = 0b00000001, |
|
||||||
HTTP_POST = 0b00000010, |
|
||||||
HTTP_DELETE = 0b00000100, |
|
||||||
HTTP_PUT = 0b00001000, |
|
||||||
HTTP_PATCH = 0b00010000, |
|
||||||
HTTP_HEAD = 0b00100000, |
|
||||||
HTTP_OPTIONS = 0b01000000, |
|
||||||
HTTP_ANY = 0b01111111, |
|
||||||
} WebRequestMethod; |
|
||||||
#endif |
|
||||||
|
|
||||||
//if this value is returned when asked for data, packet will not be sent and you will be asked for data again
|
|
||||||
#define RESPONSE_TRY_AGAIN 0xFFFFFFFF |
|
||||||
|
|
||||||
typedef uint8_t WebRequestMethodComposite; |
|
||||||
typedef std::function<void(void)> ArDisconnectHandler; |
|
||||||
|
|
||||||
/*
|
|
||||||
* PARAMETER :: Chainable object to hold GET/POST and FILE parameters |
|
||||||
* */ |
|
||||||
|
|
||||||
class AsyncWebParameter { |
|
||||||
private: |
|
||||||
String _name; |
|
||||||
String _value; |
|
||||||
size_t _size; |
|
||||||
bool _isForm; |
|
||||||
bool _isFile; |
|
||||||
|
|
||||||
public: |
|
||||||
|
|
||||||
AsyncWebParameter(const String& name, const String& value, bool form=false, bool file=false, size_t size=0): _name(name), _value(value), _size(size), _isForm(form), _isFile(file){} |
|
||||||
const String& name() const { return _name; } |
|
||||||
const String& value() const { return _value; } |
|
||||||
size_t size() const { return _size; } |
|
||||||
bool isPost() const { return _isForm; } |
|
||||||
bool isFile() const { return _isFile; } |
|
||||||
}; |
|
||||||
|
|
||||||
/*
|
|
||||||
* HEADER :: Chainable object to hold the headers |
|
||||||
* */ |
|
||||||
|
|
||||||
class AsyncWebHeader { |
|
||||||
private: |
|
||||||
String _name; |
|
||||||
String _value; |
|
||||||
|
|
||||||
public: |
|
||||||
AsyncWebHeader(const String& name, const String& value): _name(name), _value(value){} |
|
||||||
AsyncWebHeader(const String& data): _name(), _value(){ |
|
||||||
if(!data) return; |
|
||||||
int index = data.indexOf(':'); |
|
||||||
if (index < 0) return; |
|
||||||
_name = data.substring(0, index); |
|
||||||
_value = data.substring(index + 2); |
|
||||||
} |
|
||||||
~AsyncWebHeader(){} |
|
||||||
const String& name() const { return _name; } |
|
||||||
const String& value() const { return _value; } |
|
||||||
String toString() const { return String(_name+": "+_value+"\r\n"); } |
|
||||||
}; |
|
||||||
|
|
||||||
/*
|
|
||||||
* REQUEST :: Each incoming Client is wrapped inside a Request and both live together until disconnect |
|
||||||
* */ |
|
||||||
|
|
||||||
typedef enum { RCT_NOT_USED = -1, RCT_DEFAULT = 0, RCT_HTTP, RCT_WS, RCT_EVENT, RCT_MAX } RequestedConnectionType; |
|
||||||
|
|
||||||
typedef std::function<size_t(uint8_t*, size_t, size_t)> AwsResponseFiller; |
|
||||||
typedef std::function<String(const String&)> AwsTemplateProcessor; |
|
||||||
|
|
||||||
class AsyncWebServerRequest { |
|
||||||
using File = fs::File; |
|
||||||
using FS = fs::FS; |
|
||||||
friend class AsyncWebServer; |
|
||||||
friend class AsyncCallbackWebHandler; |
|
||||||
private: |
|
||||||
AsyncClient* _client; |
|
||||||
AsyncWebServer* _server; |
|
||||||
AsyncWebHandler* _handler; |
|
||||||
AsyncWebServerResponse* _response; |
|
||||||
StringArray _interestingHeaders; |
|
||||||
ArDisconnectHandler _onDisconnectfn; |
|
||||||
|
|
||||||
String _temp; |
|
||||||
uint8_t _parseState; |
|
||||||
|
|
||||||
uint8_t _version; |
|
||||||
WebRequestMethodComposite _method; |
|
||||||
String _url; |
|
||||||
String _host; |
|
||||||
String _contentType; |
|
||||||
String _boundary; |
|
||||||
String _authorization; |
|
||||||
RequestedConnectionType _reqconntype; |
|
||||||
void _removeNotInterestingHeaders(); |
|
||||||
bool _isDigest; |
|
||||||
bool _isMultipart; |
|
||||||
bool _isPlainPost; |
|
||||||
bool _expectingContinue; |
|
||||||
size_t _contentLength; |
|
||||||
size_t _parsedLength; |
|
||||||
|
|
||||||
LinkedList<AsyncWebHeader *> _headers; |
|
||||||
LinkedList<AsyncWebParameter *> _params; |
|
||||||
LinkedList<String *> _pathParams; |
|
||||||
|
|
||||||
uint8_t _multiParseState; |
|
||||||
uint8_t _boundaryPosition; |
|
||||||
size_t _itemStartIndex; |
|
||||||
size_t _itemSize; |
|
||||||
String _itemName; |
|
||||||
String _itemFilename; |
|
||||||
String _itemType; |
|
||||||
String _itemValue; |
|
||||||
uint8_t *_itemBuffer; |
|
||||||
size_t _itemBufferIndex; |
|
||||||
bool _itemIsFile; |
|
||||||
|
|
||||||
void _onPoll(); |
|
||||||
void _onAck(size_t len, uint32_t time); |
|
||||||
void _onError(int8_t error); |
|
||||||
void _onTimeout(uint32_t time); |
|
||||||
void _onDisconnect(); |
|
||||||
void _onData(void *buf, size_t len); |
|
||||||
|
|
||||||
void _addParam(AsyncWebParameter*); |
|
||||||
void _addPathParam(const char *param); |
|
||||||
|
|
||||||
bool _parseReqHead(); |
|
||||||
bool _parseReqHeader(); |
|
||||||
void _parseLine(); |
|
||||||
void _parsePlainPostChar(uint8_t data); |
|
||||||
void _parseMultipartPostByte(uint8_t data, bool last); |
|
||||||
void _addGetParams(const String& params); |
|
||||||
|
|
||||||
void _handleUploadStart(); |
|
||||||
void _handleUploadByte(uint8_t data, bool last); |
|
||||||
void _handleUploadEnd(); |
|
||||||
|
|
||||||
public: |
|
||||||
File _tempFile; |
|
||||||
void *_tempObject; |
|
||||||
|
|
||||||
AsyncWebServerRequest(AsyncWebServer*, AsyncClient*); |
|
||||||
~AsyncWebServerRequest(); |
|
||||||
|
|
||||||
AsyncClient* client(){ return _client; } |
|
||||||
uint8_t version() const { return _version; } |
|
||||||
WebRequestMethodComposite method() const { return _method; } |
|
||||||
const String& url() const { return _url; } |
|
||||||
const String& host() const { return _host; } |
|
||||||
const String& contentType() const { return _contentType; } |
|
||||||
size_t contentLength() const { return _contentLength; } |
|
||||||
bool multipart() const { return _isMultipart; } |
|
||||||
const char * methodToString() const; |
|
||||||
const char * requestedConnTypeToString() const; |
|
||||||
RequestedConnectionType requestedConnType() const { return _reqconntype; } |
|
||||||
bool isExpectedRequestedConnType(RequestedConnectionType erct1, RequestedConnectionType erct2 = RCT_NOT_USED, RequestedConnectionType erct3 = RCT_NOT_USED); |
|
||||||
void onDisconnect (ArDisconnectHandler fn); |
|
||||||
|
|
||||||
//hash is the string representation of:
|
|
||||||
// base64(user:pass) for basic or
|
|
||||||
// user:realm:md5(user:realm:pass) for digest
|
|
||||||
bool authenticate(const char * hash); |
|
||||||
bool authenticate(const char * username, const char * password, const char * realm = NULL, bool passwordIsHash = false); |
|
||||||
void requestAuthentication(const char * realm = NULL, bool isDigest = true); |
|
||||||
|
|
||||||
void setHandler(AsyncWebHandler *handler){ _handler = handler; } |
|
||||||
void addInterestingHeader(const String& name); |
|
||||||
|
|
||||||
void redirect(const String& url); |
|
||||||
|
|
||||||
void send(AsyncWebServerResponse *response); |
|
||||||
void send(int code, const String& contentType=String(), const String& content=String()); |
|
||||||
void send(FS &fs, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr); |
|
||||||
void send(File content, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr); |
|
||||||
void send(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback=nullptr); |
|
||||||
void send(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr); |
|
||||||
void sendChunked(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr); |
|
||||||
void send_P(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback=nullptr); |
|
||||||
void send_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback=nullptr); |
|
||||||
|
|
||||||
AsyncWebServerResponse *beginResponse(int code, const String& contentType=String(), const String& content=String()); |
|
||||||
AsyncWebServerResponse *beginResponse(FS &fs, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr); |
|
||||||
AsyncWebServerResponse *beginResponse(File content, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr); |
|
||||||
AsyncWebServerResponse *beginResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback=nullptr); |
|
||||||
AsyncWebServerResponse *beginResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr); |
|
||||||
AsyncWebServerResponse *beginChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr); |
|
||||||
AsyncResponseStream *beginResponseStream(const String& contentType, size_t bufferSize=1460); |
|
||||||
AsyncWebServerResponse *beginResponse_P(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback=nullptr); |
|
||||||
AsyncWebServerResponse *beginResponse_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback=nullptr); |
|
||||||
|
|
||||||
size_t headers() const; // get header count
|
|
||||||
bool hasHeader(const String& name) const; // check if header exists
|
|
||||||
bool hasHeader(const __FlashStringHelper * data) const; // check if header exists
|
|
||||||
|
|
||||||
AsyncWebHeader* getHeader(const String& name) const; |
|
||||||
AsyncWebHeader* getHeader(const __FlashStringHelper * data) const; |
|
||||||
AsyncWebHeader* getHeader(size_t num) const; |
|
||||||
|
|
||||||
size_t params() const; // get arguments count
|
|
||||||
bool hasParam(const String& name, bool post=false, bool file=false) const; |
|
||||||
bool hasParam(const __FlashStringHelper * data, bool post=false, bool file=false) const; |
|
||||||
|
|
||||||
AsyncWebParameter* getParam(const String& name, bool post=false, bool file=false) const; |
|
||||||
AsyncWebParameter* getParam(const __FlashStringHelper * data, bool post, bool file) const;
|
|
||||||
AsyncWebParameter* getParam(size_t num) const; |
|
||||||
|
|
||||||
size_t args() const { return params(); } // get arguments count
|
|
||||||
const String& arg(const String& name) const; // get request argument value by name
|
|
||||||
const String& arg(const __FlashStringHelper * data) const; // get request argument value by F(name)
|
|
||||||
const String& arg(size_t i) const; // get request argument value by number
|
|
||||||
const String& argName(size_t i) const; // get request argument name by number
|
|
||||||
bool hasArg(const char* name) const; // check if argument exists
|
|
||||||
bool hasArg(const __FlashStringHelper * data) const; // check if F(argument) exists
|
|
||||||
|
|
||||||
const String& ASYNCWEBSERVER_REGEX_ATTRIBUTE pathArg(size_t i) const; |
|
||||||
|
|
||||||
const String& header(const char* name) const;// get request header value by name
|
|
||||||
const String& header(const __FlashStringHelper * data) const;// get request header value by F(name)
|
|
||||||
const String& header(size_t i) const; // get request header value by number
|
|
||||||
const String& headerName(size_t i) const; // get request header name by number
|
|
||||||
String urlDecode(const String& text) const; |
|
||||||
}; |
|
||||||
|
|
||||||
/*
|
|
||||||
* FILTER :: Callback to filter AsyncWebRewrite and AsyncWebHandler (done by the Server) |
|
||||||
* */ |
|
||||||
|
|
||||||
typedef std::function<bool(AsyncWebServerRequest *request)> ArRequestFilterFunction; |
|
||||||
|
|
||||||
bool ON_STA_FILTER(AsyncWebServerRequest *request); |
|
||||||
|
|
||||||
bool ON_AP_FILTER(AsyncWebServerRequest *request); |
|
||||||
|
|
||||||
/*
|
|
||||||
* REWRITE :: One instance can be handle any Request (done by the Server) |
|
||||||
* */ |
|
||||||
|
|
||||||
class AsyncWebRewrite { |
|
||||||
protected: |
|
||||||
String _from; |
|
||||||
String _toUrl; |
|
||||||
String _params; |
|
||||||
ArRequestFilterFunction _filter; |
|
||||||
public: |
|
||||||
AsyncWebRewrite(const char* from, const char* to): _from(from), _toUrl(to), _params(String()), _filter(NULL){ |
|
||||||
int index = _toUrl.indexOf('?'); |
|
||||||
if (index > 0) { |
|
||||||
_params = _toUrl.substring(index +1); |
|
||||||
_toUrl = _toUrl.substring(0, index); |
|
||||||
} |
|
||||||
} |
|
||||||
virtual ~AsyncWebRewrite(){} |
|
||||||
AsyncWebRewrite& setFilter(ArRequestFilterFunction fn) { _filter = fn; return *this; } |
|
||||||
bool filter(AsyncWebServerRequest *request) const { return _filter == NULL || _filter(request); } |
|
||||||
const String& from(void) const { return _from; } |
|
||||||
const String& toUrl(void) const { return _toUrl; } |
|
||||||
const String& params(void) const { return _params; } |
|
||||||
virtual bool match(AsyncWebServerRequest *request) { return from() == request->url() && filter(request); } |
|
||||||
}; |
|
||||||
|
|
||||||
/*
|
|
||||||
* HANDLER :: One instance can be attached to any Request (done by the Server) |
|
||||||
* */ |
|
||||||
|
|
||||||
class AsyncWebHandler { |
|
||||||
protected: |
|
||||||
ArRequestFilterFunction _filter; |
|
||||||
String _username; |
|
||||||
String _password; |
|
||||||
public: |
|
||||||
AsyncWebHandler():_username(""), _password(""){} |
|
||||||
AsyncWebHandler& setFilter(ArRequestFilterFunction fn) { _filter = fn; return *this; } |
|
||||||
AsyncWebHandler& setAuthentication(const char *username, const char *password){ _username = String(username);_password = String(password); return *this; }; |
|
||||||
bool filter(AsyncWebServerRequest *request){ return _filter == NULL || _filter(request); } |
|
||||||
virtual ~AsyncWebHandler(){} |
|
||||||
virtual bool canHandle(AsyncWebServerRequest *request __attribute__((unused))){ |
|
||||||
return false; |
|
||||||
} |
|
||||||
virtual void handleRequest(AsyncWebServerRequest *request __attribute__((unused))){} |
|
||||||
virtual void handleUpload(AsyncWebServerRequest *request __attribute__((unused)), const String& filename __attribute__((unused)), size_t index __attribute__((unused)), uint8_t *data __attribute__((unused)), size_t len __attribute__((unused)), bool final __attribute__((unused))){} |
|
||||||
virtual void handleBody(AsyncWebServerRequest *request __attribute__((unused)), uint8_t *data __attribute__((unused)), size_t len __attribute__((unused)), size_t index __attribute__((unused)), size_t total __attribute__((unused))){} |
|
||||||
virtual bool isRequestHandlerTrivial(){return true;} |
|
||||||
}; |
|
||||||
|
|
||||||
/*
|
|
||||||
* RESPONSE :: One instance is created for each Request (attached by the Handler) |
|
||||||
* */ |
|
||||||
|
|
||||||
typedef enum { |
|
||||||
RESPONSE_SETUP, RESPONSE_HEADERS, RESPONSE_CONTENT, RESPONSE_WAIT_ACK, RESPONSE_END, RESPONSE_FAILED |
|
||||||
} WebResponseState; |
|
||||||
|
|
||||||
class AsyncWebServerResponse { |
|
||||||
protected: |
|
||||||
int _code; |
|
||||||
LinkedList<AsyncWebHeader *> _headers; |
|
||||||
String _contentType; |
|
||||||
size_t _contentLength; |
|
||||||
bool _sendContentLength; |
|
||||||
bool _chunked; |
|
||||||
size_t _headLength; |
|
||||||
size_t _sentLength; |
|
||||||
size_t _ackedLength; |
|
||||||
size_t _writtenLength; |
|
||||||
WebResponseState _state; |
|
||||||
const char* _responseCodeToString(int code); |
|
||||||
|
|
||||||
public: |
|
||||||
AsyncWebServerResponse(); |
|
||||||
virtual ~AsyncWebServerResponse(); |
|
||||||
virtual void setCode(int code); |
|
||||||
virtual void setContentLength(size_t len); |
|
||||||
virtual void setContentType(const String& type); |
|
||||||
virtual void addHeader(const String& name, const String& value); |
|
||||||
virtual String _assembleHead(uint8_t version); |
|
||||||
virtual bool _started() const; |
|
||||||
virtual bool _finished() const; |
|
||||||
virtual bool _failed() const; |
|
||||||
virtual bool _sourceValid() const; |
|
||||||
virtual void _respond(AsyncWebServerRequest *request); |
|
||||||
virtual size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); |
|
||||||
}; |
|
||||||
|
|
||||||
/*
|
|
||||||
* SERVER :: One instance |
|
||||||
* */ |
|
||||||
|
|
||||||
typedef std::function<void(AsyncWebServerRequest *request)> ArRequestHandlerFunction; |
|
||||||
typedef std::function<void(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final)> ArUploadHandlerFunction; |
|
||||||
typedef std::function<void(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total)> ArBodyHandlerFunction; |
|
||||||
|
|
||||||
class AsyncWebServer { |
|
||||||
protected: |
|
||||||
AsyncServer _server; |
|
||||||
LinkedList<AsyncWebRewrite*> _rewrites; |
|
||||||
LinkedList<AsyncWebHandler*> _handlers; |
|
||||||
AsyncCallbackWebHandler* _catchAllHandler; |
|
||||||
|
|
||||||
public: |
|
||||||
AsyncWebServer(uint16_t port); |
|
||||||
~AsyncWebServer(); |
|
||||||
|
|
||||||
void begin(); |
|
||||||
void end(); |
|
||||||
|
|
||||||
#if ASYNC_TCP_SSL_ENABLED |
|
||||||
void onSslFileRequest(AcSSlFileHandler cb, void* arg); |
|
||||||
void beginSecure(const char *cert, const char *private_key_file, const char *password); |
|
||||||
#endif |
|
||||||
|
|
||||||
AsyncWebRewrite& addRewrite(AsyncWebRewrite* rewrite); |
|
||||||
bool removeRewrite(AsyncWebRewrite* rewrite); |
|
||||||
AsyncWebRewrite& rewrite(const char* from, const char* to); |
|
||||||
|
|
||||||
AsyncWebHandler& addHandler(AsyncWebHandler* handler); |
|
||||||
bool removeHandler(AsyncWebHandler* handler); |
|
||||||
|
|
||||||
AsyncCallbackWebHandler& on(const char* uri, ArRequestHandlerFunction onRequest); |
|
||||||
AsyncCallbackWebHandler& on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest); |
|
||||||
AsyncCallbackWebHandler& on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload); |
|
||||||
AsyncCallbackWebHandler& on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody); |
|
||||||
|
|
||||||
AsyncStaticWebHandler& serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_control = NULL); |
|
||||||
|
|
||||||
void onNotFound(ArRequestHandlerFunction fn); //called when handler is not assigned
|
|
||||||
void onFileUpload(ArUploadHandlerFunction fn); //handle file uploads
|
|
||||||
void onRequestBody(ArBodyHandlerFunction fn); //handle posts with plain body content (JSON often transmitted this way as a request)
|
|
||||||
|
|
||||||
void reset(); //remove all writers and handlers, with onNotFound/onFileUpload/onRequestBody
|
|
||||||
|
|
||||||
void _handleDisconnect(AsyncWebServerRequest *request); |
|
||||||
void _attachHandler(AsyncWebServerRequest *request); |
|
||||||
void _rewriteRequest(AsyncWebServerRequest *request); |
|
||||||
}; |
|
||||||
|
|
||||||
class DefaultHeaders { |
|
||||||
using headers_t = LinkedList<AsyncWebHeader *>; |
|
||||||
headers_t _headers; |
|
||||||
|
|
||||||
DefaultHeaders() |
|
||||||
:_headers(headers_t([](AsyncWebHeader *h){ delete h; })) |
|
||||||
{} |
|
||||||
public: |
|
||||||
using ConstIterator = headers_t::ConstIterator; |
|
||||||
|
|
||||||
void addHeader(const String& name, const String& value){ |
|
||||||
_headers.add(new AsyncWebHeader(name, value)); |
|
||||||
}
|
|
||||||
|
|
||||||
ConstIterator begin() const { return _headers.begin(); } |
|
||||||
ConstIterator end() const { return _headers.end(); } |
|
||||||
|
|
||||||
DefaultHeaders(DefaultHeaders const &) = delete; |
|
||||||
DefaultHeaders &operator=(DefaultHeaders const &) = delete; |
|
||||||
static DefaultHeaders &Instance() { |
|
||||||
static DefaultHeaders instance; |
|
||||||
return instance; |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
#include "WebResponseImpl.h" |
|
||||||
#include "WebHandlerImpl.h" |
|
||||||
#include "AsyncWebSocket.h" |
|
||||||
#include "AsyncEventSource.h" |
|
||||||
|
|
||||||
#endif /* _AsyncWebServer_H_ */ |
|
@ -1,544 +0,0 @@ |
|||||||
#include "SPIFFSEditor.h" |
|
||||||
#include <FS.h> |
|
||||||
|
|
||||||
//File: edit.htm.gz, Size: 4151
|
|
||||||
#define edit_htm_gz_len 4151 |
|
||||||
const uint8_t edit_htm_gz[] PROGMEM = { |
|
||||||
0x1F, 0x8B, 0x08, 0x08, 0xB8, 0x94, 0xB1, 0x59, 0x00, 0x03, 0x65, 0x64, 0x69, 0x74, 0x2E, 0x68, |
|
||||||
0x74, 0x6D, 0x00, 0xB5, 0x3A, 0x0B, 0x7B, 0xDA, 0xB8, 0xB2, 0x7F, 0xC5, 0x71, 0xCF, 0x66, 0xED, |
|
||||||
0x83, 0x31, 0x90, 0xA4, 0xD9, 0xD6, 0xC4, 0xC9, 0x42, 0x92, 0x36, 0x6D, 0xF3, 0x6A, 0x80, 0xB6, |
|
||||||
0x69, 0x4F, 0xEE, 0x7E, 0xC2, 0x16, 0xA0, 0xC6, 0x96, 0x5D, 0x5B, 0x0E, 0x49, 0x59, 0xFE, 0xFB, |
|
||||||
0x9D, 0x91, 0x6C, 0xB0, 0x09, 0x69, 0x77, 0xCF, 0xBD, 0xBB, 0xDD, 0x2D, 0x92, 0x46, 0x33, 0x9A, |
|
||||||
0x19, 0xCD, 0x53, 0xDE, 0xBD, 0x8D, 0xA3, 0x8B, 0xC3, 0xFE, 0xF5, 0xE5, 0xB1, 0x36, 0x11, 0x61, |
|
||||||
0xB0, 0xBF, 0x87, 0x7F, 0x6B, 0x01, 0xE1, 0x63, 0x97, 0xF2, 0xFD, 0x3D, 0xC1, 0x44, 0x40, 0xF7, |
|
||||||
0x8F, 0x7B, 0x97, 0xDA, 0xB1, 0xCF, 0x44, 0x94, 0xEC, 0x35, 0xD4, 0xCA, 0x5E, 0x2A, 0x1E, 0x02, |
|
||||||
0xAA, 0x85, 0xD4, 0x67, 0xC4, 0x4D, 0xBD, 0x84, 0xC2, 0x66, 0xDB, 0x0B, 0x67, 0xDF, 0xEB, 0x8C, |
|
||||||
0xFB, 0xF4, 0xDE, 0xD9, 0x6E, 0x36, 0xDB, 0x71, 0x94, 0x32, 0xC1, 0x22, 0xEE, 0x90, 0x61, 0x1A, |
|
||||||
0x05, 0x99, 0xA0, 0xED, 0x80, 0x8E, 0x84, 0xF3, 0x3C, 0xBE, 0x6F, 0x0F, 0xA3, 0xC4, 0xA7, 0x89, |
|
||||||
0xD3, 0x8A, 0xEF, 0x35, 0x00, 0x31, 0x5F, 0x7B, 0xB6, 0xB3, 0xB3, 0xD3, 0x1E, 0x12, 0xEF, 0x76, |
|
||||||
0x9C, 0x44, 0x19, 0xF7, 0xEB, 0x5E, 0x14, 0x44, 0x89, 0xF3, 0x6C, 0xF4, 0x1C, 0xFF, 0xB4, 0x7D, |
|
||||||
0x96, 0xC6, 0x01, 0x79, 0x70, 0x78, 0xC4, 0x29, 0xE0, 0xDE, 0xD7, 0xD3, 0x09, 0xF1, 0xA3, 0xA9, |
|
||||||
0xD3, 0xD4, 0x9A, 0x5A, 0xAB, 0x09, 0x44, 0x92, 0xF1, 0x90, 0x18, 0x4D, 0x0B, 0xFF, 0xD8, 0x3B, |
|
||||||
0x66, 0x7B, 0x14, 0x71, 0x51, 0x4F, 0xD9, 0x77, 0xEA, 0xB4, 0xB6, 0xE0, 0x34, 0x39, 0x1D, 0x91, |
|
||||||
0x90, 0x05, 0x0F, 0x4E, 0x4A, 0x78, 0x5A, 0x4F, 0x69, 0xC2, 0x46, 0x6A, 0x79, 0x4A, 0xD9, 0x78, |
|
||||||
0x22, 0x9C, 0xDF, 0x9A, 0xCD, 0x39, 0xF0, 0xAF, 0x65, 0xC1, 0x2C, 0x60, 0x29, 0x20, 0xA3, 0x78, |
|
||||||
0xEA, 0x3C, 0x11, 0xC5, 0x4E, 0x53, 0xB1, 0xDE, 0x6C, 0x87, 0x24, 0x19, 0x33, 0x0E, 0x83, 0x98, |
|
||||||
0xF8, 0x3E, 0xE3, 0x63, 0x47, 0xA1, 0x05, 0x6C, 0xB6, 0x90, 0x36, 0xA1, 0x01, 0x11, 0xEC, 0x8E, |
|
||||||
0xB6, 0x43, 0xC6, 0xEB, 0x53, 0xE6, 0x8B, 0x89, 0xB3, 0x0B, 0x3C, 0xB6, 0xBD, 0x2C, 0x49, 0x41, |
|
||||||
0xA6, 0x38, 0x62, 0x5C, 0xD0, 0x44, 0xA2, 0xA5, 0x31, 0xE1, 0xB3, 0x5C, 0x54, 0x54, 0x40, 0x21, |
|
||||||
0x27, 0xE3, 0x01, 0xE3, 0xB4, 0x3E, 0x0C, 0x22, 0xEF, 0x76, 0x71, 0xD2, 0x6E, 0x7C, 0x9F, 0x9F, |
|
||||||
0xE5, 0x4C, 0xA2, 0x3B, 0x9A, 0xCC, 0x96, 0xEA, 0x92, 0xD8, 0x15, 0x60, 0x85, 0x34, 0xA5, 0x74, |
|
||||||
0x6E, 0x8B, 0xBB, 0x0C, 0xA0, 0x96, 0xFC, 0x05, 0x29, 0x17, 0xFC, 0x2F, 0x45, 0x5A, 0x11, 0x5C, |
|
||||||
0xA1, 0x30, 0x1E, 0x67, 0x62, 0xF6, 0xF8, 0x2A, 0xA3, 0x98, 0x78, 0x4C, 0x3C, 0xA0, 0xFC, 0xB0, |
|
||||||
0x6D, 0x86, 0xBA, 0x04, 0xAC, 0x24, 0x24, 0x81, 0x86, 0x3A, 0xD7, 0x3E, 0xD0, 0xC4, 0x27, 0x9C, |
|
||||||
0x58, 0x9D, 0x84, 0x91, 0xC0, 0xEA, 0x2D, 0xB5, 0x5E, 0x0F, 0xA3, 0xEF, 0xF5, 0x0C, 0xC6, 0x30, |
|
||||||
0x0F, 0xA8, 0x27, 0x94, 0x92, 0xE1, 0x1E, 0x86, 0xB7, 0x4C, 0x3C, 0x06, 0x3C, 0x5A, 0x28, 0xA9, |
|
||||||
0x4B, 0x2A, 0x69, 0xA2, 0x2E, 0xB0, 0x25, 0xD5, 0x83, 0x1C, 0x4B, 0xC9, 0x95, 0x50, 0xF5, 0x61, |
|
||||||
0x24, 0x44, 0x14, 0x4A, 0x93, 0x5B, 0x08, 0xAC, 0x49, 0xAB, 0x79, 0xF1, 0xE8, 0x46, 0xD6, 0x6B, |
|
||||||
0xBF, 0x44, 0xBE, 0x0D, 0x7A, 0x15, 0xCC, 0x23, 0x41, 0x9D, 0x04, 0x6C, 0xCC, 0x9D, 0x90, 0xF9, |
|
||||||
0x7E, 0x40, 0x4B, 0x56, 0xEB, 0x64, 0x49, 0x60, 0xF8, 0x44, 0x10, 0x87, 0x85, 0x64, 0x4C, 0x1B, |
|
||||||
0x31, 0x1F, 0x03, 0x34, 0xA5, 0xBB, 0x3B, 0x16, 0xFB, 0xD0, 0xBD, 0xB8, 0x9A, 0x36, 0xDF, 0xBD, |
|
||||||
0x1E, 0x47, 0x1D, 0xF8, 0xE7, 0xBC, 0x37, 0x98, 0x1C, 0x0F, 0xC6, 0x30, 0xEA, 0xE2, 0xB4, 0xF3, |
|
||||||
0xFE, 0xB0, 0xF3, 0x1E, 0x7E, 0x0E, 0x5B, 0xB5, 0xAF, 0xA3, 0x6F, 0xB8, 0xD0, 0x7D, 0xED, 0x77, |
|
||||||
0xFB, 0x83, 0xE3, 0x4E, 0xE7, 0x5D, 0xE3, 0xCD, 0xF9, 0xF4, 0xE3, 0xBB, 0x5D, 0x04, 0x77, 0x83, |
|
||||||
0xE6, 0xD5, 0x87, 0x49, 0x73, 0xB0, 0xF5, 0x32, 0xF4, 0x4F, 0xFC, 0x89, 0x17, 0x0E, 0x3A, 0xEF, |
|
||||||
0x3F, 0x5E, 0xDD, 0x5D, 0x87, 0x83, 0x71, 0xEF, 0x63, 0x6B, 0xF2, 0x79, 0xEB, 0x43, 0xEF, 0xF3, |
|
||||||
0xC7, 0x57, 0xB7, 0xF4, 0xD3, 0xC9, 0xDB, 0xCF, 0xFD, 0x29, 0x20, 0x1C, 0x45, 0xBD, 0xC1, 0x55, |
|
||||||
0xF7, 0x43, 0x77, 0xFC, 0xB9, 0xEB, 0x1D, 0xDF, 0x0F, 0x83, 0xF3, 0xEE, 0xEB, 0xCE, 0xB0, 0xB3, |
|
||||||
0xE5, 0x51, 0x3A, 0xEE, 0x5F, 0x75, 0xB3, 0x37, 0xEF, 0x2E, 0xC6, 0x8C, 0x4D, 0x7A, 0x9F, 0xCF, |
|
||||||
0xFB, 0xDE, 0xE1, 0xF3, 0xD3, 0xC1, 0x49, 0x87, 0x4D, 0xCE, 0xDF, 0x5E, 0x35, 0x6F, 0x5F, 0xBF, |
|
||||||
0x3B, 0x3C, 0xF2, 0xAE, 0xDF, 0x5E, 0xEF, 0x1E, 0x6D, 0x37, 0x7E, 0xFB, 0xED, 0xCC, 0xBF, 0x60, |
|
||||||
0xBC, 0x7F, 0xF7, 0xBD, 0x33, 0x3E, 0x9C, 0xBE, 0x78, 0x48, 0xFB, 0x93, 0x37, 0x77, 0xBC, 0xF1, |
|
||||||
0x21, 0xFA, 0xFA, 0xE6, 0xE1, 0x0C, 0xFE, 0xBB, 0xBC, 0xAC, 0x0D, 0x7B, 0xAD, 0x74, 0xF0, 0xFE, |
|
||||||
0xCD, 0x87, 0xAD, 0xF4, 0xE5, 0xF3, 0xB8, 0x7B, 0x74, 0x74, 0x17, 0x0E, 0x2F, 0x1B, 0xA1, 0x7F, |
|
||||||
0x3B, 0x12, 0x2F, 0xB6, 0x45, 0x7C, 0x3D, 0xCE, 0x3E, 0x7F, 0x7B, 0xFE, 0x76, 0xD2, 0xB8, 0xA0, |
|
||||||
0xE4, 0x7A, 0x52, 0x7B, 0xF8, 0xFE, 0xF0, 0x62, 0xD2, 0x3F, 0xB9, 0x3B, 0x0F, 0xC8, 0xFD, 0xF9, |
|
||||||
0xB9, 0xF7, 0x3D, 0xAC, 0x05, 0xE4, 0xE5, 0x45, 0x3F, 0x20, 0x49, 0x6B, 0xE0, 0x77, 0x1A, 0xB5, |
|
||||||
0xC3, 0xAD, 0xCE, 0x8E, 0x48, 0xAE, 0x0E, 0xF9, 0xD1, 0xF6, 0xD7, 0xDE, 0x8B, 0x6E, 0xB7, 0x15, |
|
||||||
0x0D, 0xBF, 0x6D, 0xBD, 0xBE, 0xDD, 0x7D, 0x3D, 0xD8, 0x7D, 0x3F, 0x7C, 0xDF, 0xE9, 0xED, 0x74, |
|
||||||
0x07, 0xE4, 0xBA, 0xF7, 0xBE, 0x33, 0xDA, 0x19, 0x4E, 0x26, 0xEF, 0xDE, 0xF5, 0x5F, 0xF9, 0x9D, |
|
||||||
0xEF, 0x49, 0xE7, 0x62, 0xDA, 0xB9, 0x3F, 0x1E, 0x74, 0x4E, 0x6A, 0xEF, 0x8E, 0xCF, 0x9A, 0xAD, |
|
||||||
0xDE, 0xF5, 0xF6, 0xF8, 0x6C, 0x77, 0xDA, 0x4D, 0x8F, 0x3B, 0xEF, 0xBB, 0xCD, 0xF1, 0xDB, 0x5A, |
|
||||||
0x48, 0x3E, 0x47, 0x87, 0xDB, 0xE3, 0x37, 0xBB, 0xEC, 0xF2, 0x9A, 0x74, 0xDE, 0x74, 0xDF, 0xA6, |
|
||||||
0xEC, 0x2A, 0x3C, 0x19, 0x34, 0x3B, 0x9D, 0xD3, 0x0B, 0xFA, 0xEA, 0x70, 0x9B, 0xBC, 0xDB, 0xF2, |
|
||||||
0x3E, 0x82, 0xFE, 0x07, 0x9F, 0xE8, 0x6F, 0xB5, 0xCE, 0xF4, 0xA2, 0x19, 0x78, 0x2F, 0x69, 0xFF, |
|
||||||
0xE4, 0xBA, 0x2F, 0x6F, 0xE7, 0x38, 0x78, 0xD5, 0xBF, 0xED, 0x65, 0xEF, 0xC3, 0xC3, 0x43, 0x53, |
|
||||||
0xE3, 0x51, 0x3D, 0xA1, 0x31, 0x25, 0xA2, 0x1C, 0xAE, 0x16, 0xFE, 0x01, 0xB6, 0xB5, 0xB4, 0xC2, |
|
||||||
0xDC, 0x4F, 0x05, 0xBD, 0x17, 0x75, 0x9F, 0x7A, 0x51, 0x42, 0xE4, 0x1E, 0x40, 0xA0, 0x09, 0x9A, |
|
||||||
0xD8, 0xFC, 0x77, 0x19, 0x3F, 0x35, 0x15, 0x3F, 0x35, 0xC2, 0x7D, 0xCD, 0x28, 0x1C, 0x01, 0x83, |
|
||||||
0x87, 0x4F, 0xEF, 0x98, 0x47, 0xEB, 0x31, 0xBB, 0xA7, 0x41, 0x5D, 0x22, 0x3B, 0x4D, 0x73, 0x26, |
|
||||||
0xFD, 0xAD, 0xD8, 0x46, 0x38, 0x98, 0x9A, 0xA4, 0x5A, 0x2C, 0xF8, 0x5F, 0x89, 0x47, 0x21, 0xB0, |
|
||||||
0x81, 0xCB, 0x84, 0xF8, 0xAB, 0x7C, 0x27, 0x4A, 0xEA, 0xC3, 0x6C, 0x3C, 0x62, 0xF7, 0xE0, 0xD0, |
|
||||||
0x23, 0xC6, 0x99, 0xA0, 0x5A, 0x2B, 0x9D, 0xFF, 0x5E, 0x90, 0xB9, 0xA5, 0x0F, 0xA3, 0x84, 0x84, |
|
||||||
0x34, 0xD5, 0xFE, 0x22, 0x99, 0xD9, 0x28, 0x89, 0xC2, 0x65, 0x10, 0x99, 0x8B, 0xA8, 0x34, 0x99, |
|
||||||
0xCF, 0x9F, 0x65, 0x71, 0x10, 0x11, 0x10, 0x73, 0x4D, 0xE4, 0x50, 0xF1, 0x34, 0x91, 0x6E, 0xB5, |
|
||||||
0x88, 0xAB, 0xB9, 0x9B, 0x6D, 0xA1, 0x5B, 0x96, 0xDD, 0x7A, 0x6B, 0x67, 0xE9, 0xBA, 0x75, 0xB9, |
|
||||||
0x17, 0xE3, 0xFD, 0x9A, 0x4C, 0x81, 0xF1, 0xA0, 0x14, 0xEE, 0x9E, 0x09, 0x50, 0xE9, 0x13, 0x87, |
|
||||||
0xCB, 0x43, 0xF2, 0xC8, 0xB0, 0x60, 0x40, 0x05, 0xEA, 0x96, 0x8C, 0xD4, 0x85, 0x24, 0xB0, 0x6F, |
|
||||||
0xFE, 0x8C, 0xCA, 0xBC, 0x67, 0x3D, 0x8B, 0x13, 0xB8, 0x0D, 0x3A, 0xFD, 0x11, 0xCD, 0x42, 0xA6, |
|
||||||
0x2A, 0x6D, 0x45, 0x53, 0x65, 0xBC, 0x5C, 0x84, 0x65, 0xDA, 0x93, 0xBC, 0x16, 0xA4, 0x1F, 0x4B, |
|
||||||
0x05, 0xE0, 0x05, 0x37, 0xCF, 0x91, 0x9B, 0x1F, 0x6A, 0x75, 0x7B, 0xF7, 0x97, 0x9C, 0x87, 0x9D, |
|
||||||
0xE6, 0x2F, 0x73, 0x3B, 0xDF, 0x5B, 0xA4, 0xE4, 0x56, 0x13, 0xFE, 0x29, 0x32, 0xEF, 0x8B, 0x25, |
|
||||||
0x0B, 0xC3, 0xE7, 0xF8, 0xA7, 0x60, 0x10, 0xE9, 0x94, 0x80, 0xDB, 0x3B, 0x2F, 0x5F, 0xF8, 0xC3, |
|
||||||
0x02, 0x98, 0x0B, 0xF6, 0x24, 0x3C, 0x21, 0x3E, 0xCB, 0x52, 0xE7, 0x79, 0xF3, 0x97, 0x5C, 0x9F, |
|
||||||
0x5B, 0x3B, 0x28, 0xFB, 0xE2, 0x2E, 0x71, 0xB2, 0xB4, 0xD8, 0x34, 0x66, 0x5C, 0xDB, 0x4A, 0x35, |
|
||||||
0xBC, 0x6F, 0x92, 0x2C, 0x0C, 0xB3, 0x92, 0xED, 0xE7, 0xBF, 0x2F, 0x4D, 0x13, 0xF7, 0xCF, 0x9A, |
|
||||||
0xBF, 0xCC, 0x44, 0x02, 0xD9, 0x64, 0x04, 0xB9, 0xC6, 0x49, 0x22, 0x41, 0x04, 0x35, 0x9A, 0xE6, |
|
||||||
0x1C, 0x84, 0x5B, 0x03, 0xD8, 0xDE, 0x6D, 0xFA, 0x74, 0x6C, 0xCE, 0xE7, 0x7B, 0x0D, 0x99, 0xD7, |
|
||||||
0xA0, 0x6C, 0xF1, 0x12, 0x16, 0x8B, 0xFD, 0x51, 0xC6, 0x3D, 0xE4, 0x41, 0x1B, 0x53, 0x83, 0x9A, |
|
||||||
0xB3, 0x84, 0x8A, 0x2C, 0xE1, 0x9A, 0x1F, 0x79, 0x19, 0x1A, 0xBB, 0x3D, 0xA6, 0xE2, 0x58, 0xD9, |
|
||||||
0x7D, 0xF7, 0xE1, 0x8D, 0x0F, 0x3B, 0xE6, 0x0B, 0x04, 0x6F, 0x2D, 0x02, 0x38, 0x30, 0x9C, 0x97, |
|
||||||
0xE3, 0x54, 0xF6, 0x43, 0x82, 0x01, 0x22, 0xEF, 0xE8, 0x83, 0x41, 0x2D, 0xB1, 0x40, 0xA4, 0x36, |
|
||||||
0xAE, 0x1B, 0xC5, 0x2E, 0x80, 0x71, 0x73, 0x76, 0x07, 0x4A, 0x20, 0x2E, 0xFD, 0x22, 0x6E, 0x2C, |
|
||||||
0xE6, 0x72, 0xF8, 0x69, 0xE7, 0xBB, 0xC9, 0x1E, 0x3B, 0xA8, 0xB7, 0x1C, 0xB2, 0xCF, 0x0E, 0x5A, |
|
||||||
0xE0, 0x5E, 0x65, 0x6E, 0xE4, 0xB9, 0xAF, 0x58, 0x40, 0x07, 0xB9, 0xC3, 0xE1, 0x31, 0x48, 0x6C, |
|
||||||
0xB1, 0x85, 0x28, 0xE2, 0x5B, 0xCD, 0xE6, 0x86, 0x4B, 0x0F, 0x48, 0x00, 0x39, 0xCC, 0xD0, 0x8F, |
|
||||||
0xAF, 0xAE, 0x2E, 0xAE, 0xBE, 0xE8, 0x35, 0x5A, 0xD3, 0x6F, 0x1C, 0x4D, 0xAF, 0x71, 0xD3, 0x11, |
|
||||||
0x76, 0x42, 0x47, 0x09, 0x4D, 0x27, 0x97, 0x44, 0x4C, 0x8C, 0xD4, 0xBE, 0x23, 0x41, 0x56, 0x16, |
|
||||||
0x84, 0xA1, 0xDC, 0xC8, 0xA2, 0x70, 0x39, 0x9D, 0x6A, 0xAF, 0x40, 0xCD, 0x47, 0x90, 0xEA, 0xDA, |
|
||||||
0xC2, 0x26, 0x71, 0x4C, 0xB9, 0x6F, 0xE8, 0x31, 0x20, 0xEA, 0x16, 0x35, 0xAD, 0x84, 0x7E, 0xCB, |
|
||||||
0x68, 0x2A, 0x52, 0x1B, 0x2C, 0xD7, 0xD0, 0x2F, 0x07, 0x7D, 0xDD, 0xD2, 0x1B, 0xE8, 0x47, 0x3A, |
|
||||||
0xF0, 0x46, 0xCC, 0x39, 0x52, 0x89, 0x5C, 0xD0, 0xA4, 0x3E, 0xCC, 0xC0, 0xA0, 0xB8, 0x6E, 0xB6, |
|
||||||
0x23, 0x9B, 0x71, 0x4E, 0x93, 0x93, 0xFE, 0xD9, 0xA9, 0xAB, 0x5F, 0x29, 0x46, 0xB4, 0x53, 0x28, |
|
||||||
0x48, 0x74, 0x4B, 0x5E, 0x51, 0x7E, 0xC8, 0xE1, 0x84, 0x05, 0xBE, 0x11, 0x99, 0x6D, 0x24, 0xE1, |
|
||||||
0x49, 0x12, 0xB2, 0x40, 0x01, 0x0A, 0x9E, 0x2D, 0x1E, 0x62, 0xEA, 0xEA, 0x23, 0x50, 0x86, 0x6E, |
|
||||||
0x79, 0x76, 0x98, 0x05, 0x82, 0xC5, 0x01, 0x75, 0x37, 0x5A, 0x30, 0xE3, 0x60, 0x41, 0xAE, 0x8E, |
|
||||||
0xB9, 0x19, 0x61, 0xCC, 0x77, 0x75, 0x15, 0xA1, 0xF2, 0xB8, 0xB6, 0xEE, 0x14, 0x4F, 0x9D, 0x92, |
|
||||||
0x56, 0x4E, 0x49, 0xCB, 0xB8, 0x4A, 0xE0, 0x34, 0x3F, 0x18, 0xC3, 0x3C, 0xCE, 0xD4, 0x51, 0x05, |
|
||||||
0xCC, 0xA7, 0x23, 0x02, 0x9C, 0x7C, 0x40, 0x6D, 0xBA, 0x7A, 0x63, 0xDD, 0x41, 0xA9, 0x3A, 0xC8, |
|
||||||
0xAF, 0x6A, 0xC4, 0x2F, 0x6B, 0x44, 0xDD, 0xEE, 0x3A, 0x64, 0x5F, 0x21, 0x07, 0x55, 0xE4, 0xA0, |
|
||||||
0x8C, 0x7C, 0x28, 0x8D, 0x64, 0x1D, 0x72, 0xA0, 0x90, 0x93, 0x8A, 0x88, 0x89, 0x14, 0x51, 0x85, |
|
||||||
0xBD, 0x3A, 0x6A, 0x13, 0x05, 0xD2, 0xAD, 0xA4, 0x22, 0x66, 0x62, 0x83, 0x97, 0x92, 0x61, 0x40, |
|
||||||
0x7D, 0x77, 0xA3, 0x09, 0x33, 0x2C, 0xB6, 0xDD, 0xAD, 0xE6, 0x9A, 0x33, 0x12, 0x75, 0x46, 0x56, |
|
||||||
0x65, 0x30, 0x2B, 0x33, 0xA8, 0xF5, 0xC8, 0x1D, 0xD5, 0xD6, 0x31, 0x98, 0x99, 0x56, 0x60, 0x47, |
|
||||||
0xDC, 0x0B, 0x98, 0x77, 0xEB, 0x2E, 0xBD, 0xC5, 0x9C, 0xB1, 0x85, 0x85, 0x5A, 0x5C, 0x06, 0xBA, |
|
||||||
0x01, 0x94, 0x5E, 0x8B, 0xA5, 0x7C, 0x80, 0xFA, 0x9E, 0x5B, 0xD9, 0x5A, 0x02, 0xDC, 0xA6, 0xF7, |
|
||||||
0xD4, 0x3B, 0x8C, 0xC2, 0x90, 0xA0, 0xED, 0xA6, 0xC0, 0x41, 0x3E, 0xD1, 0xCD, 0xB9, 0x15, 0xAD, |
|
||||||
0xC5, 0x79, 0xC2, 0x45, 0x2C, 0x7F, 0x3D, 0x8B, 0x23, 0x03, 0x5C, 0xCE, 0xF5, 0x6C, 0xD4, 0x61, |
|
||||||
0x6A, 0x83, 0x1E, 0xC7, 0x62, 0xF2, 0x13, 0x17, 0x2A, 0x0C, 0x54, 0xA2, 0x7C, 0x69, 0xDE, 0x58, |
|
||||||
0x0B, 0x91, 0x56, 0x7C, 0xEA, 0xA2, 0xB7, 0xE2, 0x54, 0xA8, 0xBC, 0x8A, 0x5D, 0x9A, 0x4B, 0x1D, |
|
||||||
0x94, 0x61, 0xB9, 0xBD, 0x2F, 0xA0, 0xFA, 0x7C, 0x0E, 0xE7, 0x01, 0xFF, 0x13, 0x68, 0xF9, 0xE8, |
|
||||||
0x5F, 0x17, 0x60, 0xC9, 0xA3, 0x34, 0x78, 0x8B, 0xBB, 0x0D, 0xE3, 0xC0, 0xF9, 0x8F, 0x6D, 0x7C, |
|
||||||
0xF9, 0x1F, 0xFB, 0xA6, 0x66, 0x9A, 0x07, 0xFF, 0x6A, 0x48, 0x0D, 0x1B, 0xC2, 0xFC, 0xD2, 0xBA, |
|
||||||
0xB1, 0x08, 0x80, 0xED, 0x7F, 0x9B, 0xFF, 0xB1, 0x25, 0xB8, 0x02, 0x6B, 0xDF, 0x45, 0x90, 0x49, |
|
||||||
0xF0, 0x24, 0x34, 0xB0, 0x68, 0xA4, 0x91, 0xCD, 0x4D, 0x43, 0xB8, 0xA4, 0x72, 0x8D, 0x35, 0x51, |
|
||||||
0xD3, 0x6D, 0x88, 0x53, 0x50, 0x5B, 0xAC, 0x04, 0xBF, 0x3E, 0x24, 0x7A, 0x15, 0x5B, 0x17, 0x00, |
|
||||||
0xC9, 0x3D, 0xCA, 0x0C, 0x3D, 0x22, 0x97, 0x52, 0xCB, 0x0C, 0x02, 0x42, 0xA7, 0x89, 0xE7, 0x2A, |
|
||||||
0xAD, 0x1D, 0x14, 0x30, 0x17, 0xA2, 0xE0, 0xBC, 0x1C, 0x2D, 0x15, 0xEA, 0xAA, 0xFD, 0x17, 0x0A, |
|
||||||
0xA3, 0xD6, 0x12, 0x8A, 0x04, 0x31, 0xAD, 0xD8, 0x79, 0xC6, 0x72, 0x75, 0x4C, 0x59, 0xBA, 0x35, |
|
||||||
0x59, 0x5D, 0x96, 0xAD, 0x04, 0xAE, 0x2F, 0x8D, 0xFE, 0xD7, 0x3D, 0x16, 0x8E, 0xB5, 0x12, 0x3F, |
|
||||||
0xF8, 0x97, 0xFB, 0x2B, 0x46, 0xE4, 0xCD, 0x3F, 0xBC, 0x21, 0x70, 0x05, 0xA6, 0x41, 0x6D, 0x1E, |
|
||||||
0x4D, 0x0D, 0xB3, 0xF6, 0xAB, 0xAE, 0x49, 0x8A, 0xAE, 0x1E, 0x92, 0xFB, 0xBC, 0xA7, 0xC4, 0x8C, |
|
||||||
0xD7, 0xD6, 0x70, 0x5E, 0xB4, 0x28, 0xF9, 0x82, 0xEC, 0xE6, 0x48, 0x26, 0xA2, 0xB6, 0x56, 0x64, |
|
||||||
0x52, 0xD5, 0xCA, 0xE8, 0x5A, 0x63, 0xFF, 0xD7, 0x4A, 0x40, 0xB7, 0x98, 0xBA, 0x4E, 0x15, 0x8C, |
|
||||||
0xB3, 0x00, 0x1C, 0x93, 0x3E, 0x1D, 0x69, 0x03, 0x26, 0x03, 0x75, 0x35, 0x46, 0x5A, 0x81, 0xC1, |
|
||||||
0xCC, 0x03, 0xC3, 0x2B, 0xFB, 0xF3, 0x1E, 0x16, 0xBF, 0xFB, 0x97, 0xAA, 0xAA, 0x81, 0xD4, 0x8B, |
|
||||||
0x33, 0x5D, 0x59, 0x59, 0xD5, 0x4B, 0xE0, 0xD2, 0x08, 0xA0, 0x5B, 0x8B, 0x3C, 0x3A, 0x8C, 0xFC, |
|
||||||
0x87, 0x52, 0xF6, 0x4D, 0xBB, 0x0F, 0x87, 0x01, 0x49, 0xD3, 0x73, 0xB8, 0x01, 0x43, 0xF7, 0x42, |
|
||||||
0x50, 0xB8, 0xB2, 0xC2, 0xFD, 0xE6, 0xE6, 0x66, 0x15, 0x29, 0xA1, 0x21, 0x14, 0xDB, 0x8A, 0x2B, |
|
||||||
0xF0, 0x49, 0xD3, 0xF1, 0x81, 0x30, 0x18, 0xD2, 0x1A, 0xC6, 0xF0, 0x25, 0xE3, 0x47, 0x5C, 0x71, |
|
||||||
0xF4, 0xF4, 0x22, 0xA6, 0xFC, 0x33, 0xDC, 0x95, 0x32, 0xCB, 0x1A, 0xAD, 0xA6, 0x68, 0xFA, 0x8F, |
|
||||||
0xD8, 0x3E, 0xCA, 0x0D, 0x76, 0xC1, 0x7A, 0xBA, 0x56, 0xA1, 0xFC, 0x9F, 0x61, 0xB9, 0x94, 0x28, |
|
||||||
0xD6, 0x70, 0x9C, 0x40, 0x80, 0x5A, 0xC3, 0x31, 0xC4, 0x1A, 0x41, 0x17, 0xFC, 0x26, 0x6B, 0xF9, |
|
||||||
0xCD, 0xFE, 0x19, 0x7E, 0x97, 0x76, 0x1E, 0x15, 0x25, 0x91, 0xAA, 0xAF, 0x50, 0x02, 0x9F, 0xDD, |
|
||||||
0xE9, 0xA6, 0x15, 0xB9, 0x55, 0x0A, 0x50, 0x1B, 0x46, 0x41, 0xD0, 0x8F, 0xE2, 0x83, 0x27, 0xD6, |
|
||||||
0x9D, 0xC5, 0x7A, 0x31, 0xC8, 0xD9, 0x5C, 0x6E, 0xB1, 0xBC, 0xB5, 0x44, 0x4F, 0xA1, 0xEC, 0x5F, |
|
||||||
0x4B, 0x15, 0x01, 0x3F, 0x23, 0x8B, 0x7B, 0xAC, 0xD4, 0xA5, 0x36, 0x28, 0x0F, 0x56, 0x3F, 0xD5, |
|
||||||
0x3C, 0xCB, 0x5F, 0xCC, 0xAE, 0x6B, 0x51, 0x9B, 0xC0, 0x38, 0x57, 0x92, 0x8B, 0x4A, 0xB2, 0xC8, |
|
||||||
0x13, 0x01, 0xA8, 0x58, 0xC7, 0x2E, 0xC4, 0x4D, 0x6B, 0x7A, 0x7C, 0xBF, 0x5C, 0x83, 0xC2, 0xDF, |
|
||||||
0xF5, 0xD5, 0x12, 0x33, 0x08, 0xC4, 0xD3, 0x95, 0x4B, 0x29, 0x5F, 0x37, 0x29, 0x8A, 0x0E, 0x62, |
|
||||||
0x47, 0xA3, 0x51, 0x4A, 0xC5, 0x47, 0x0C, 0x49, 0x56, 0xB2, 0x98, 0x9F, 0xC8, 0x90, 0x04, 0x8C, |
|
||||||
0x45, 0x3C, 0x8C, 0xB2, 0x94, 0x46, 0x99, 0xA8, 0xA4, 0x16, 0x63, 0x21, 0xCC, 0x5E, 0xFA, 0xE7, |
|
||||||
0x9F, 0x8B, 0xC9, 0x7E, 0x5A, 0x0B, 0x96, 0xD3, 0xEB, 0x3D, 0xBF, 0x34, 0xD9, 0xF7, 0x6B, 0x89, |
|
||||||
0xB9, 0x7A, 0xE9, 0xFF, 0x67, 0x4B, 0x21, 0x65, 0x4B, 0xF1, 0xB0, 0x54, 0x2E, 0x62, 0x62, 0x29, |
|
||||||
0xE6, 0xC9, 0x82, 0x91, 0x97, 0x7C, 0x16, 0x0D, 0x1A, 0x2B, 0x25, 0x55, 0x9E, 0x97, 0x7D, 0x95, |
|
||||||
0x43, 0x40, 0x59, 0x71, 0xE5, 0x35, 0x11, 0x06, 0x34, 0xE0, 0x63, 0x64, 0xF2, 0x41, 0xEB, 0xA7, |
|
||||||
0xD1, 0x94, 0x26, 0x87, 0x24, 0xA5, 0x06, 0x24, 0xCD, 0x65, 0xDC, 0x41, 0xA8, 0xE9, 0x04, 0xEB, |
|
||||||
0x76, 0x6D, 0x6E, 0x12, 0x05, 0xCE, 0x33, 0x77, 0xC4, 0xB1, 0x26, 0x03, 0xF9, 0xB2, 0xCA, 0x09, |
|
||||||
0xD4, 0xC6, 0xBE, 0x12, 0xA4, 0x3E, 0x52, 0x25, 0xA8, 0x61, 0x5A, 0xD0, 0x76, 0xC0, 0x35, 0x5F, |
|
||||||
0x26, 0x51, 0x4C, 0xC6, 0xB2, 0x07, 0x83, 0x35, 0x74, 0x0F, 0xA4, 0x66, 0x6D, 0x34, 0x91, 0x60, |
|
||||||
0xA9, 0x73, 0x29, 0xFC, 0x66, 0xD9, 0xC2, 0x70, 0x4B, 0x57, 0xC9, 0xB0, 0xBD, 0xF4, 0xA5, 0x35, |
|
||||||
0x59, 0x83, 0xE0, 0x0B, 0x6C, 0x62, 0xE0, 0x1E, 0x68, 0x64, 0xF2, 0x7B, 0x00, 0x77, 0x6B, 0xB6, |
|
||||||
0xA3, 0x3D, 0xD6, 0x8E, 0x6A, 0x35, 0x53, 0x55, 0xE9, 0xAE, 0x0B, 0x6D, 0x4E, 0x74, 0x23, 0x0B, |
|
||||||
0x4B, 0x10, 0xAA, 0x9A, 0x59, 0x0C, 0x38, 0x1B, 0x81, 0xAA, 0xBA, 0xC0, 0x11, 0xD6, 0x98, 0x66, |
|
||||||
0xA9, 0x23, 0xF1, 0x97, 0x1D, 0xC9, 0x13, 0xB5, 0x07, 0x95, 0xF5, 0x05, 0xD4, 0x31, 0xAB, 0x25, |
|
||||||
0x86, 0x30, 0xD3, 0x29, 0x13, 0xDE, 0x04, 0x03, 0x90, 0x07, 0x5A, 0xD5, 0x05, 0x14, 0xB5, 0x8E, |
|
||||||
0x1C, 0x4D, 0x44, 0xB8, 0x1C, 0x05, 0xF9, 0xF0, 0x6B, 0x9A, 0x0F, 0xBC, 0xB4, 0x18, 0xDD, 0x97, |
|
||||||
0x80, 0x50, 0xD2, 0xE6, 0xE0, 0x88, 0x8F, 0xF2, 0x21, 0xF4, 0xB2, 0x05, 0x9D, 0x02, 0x58, 0xFC, |
|
||||||
0xC6, 0x71, 0x3E, 0x8A, 0x27, 0xC5, 0x68, 0x42, 0xEF, 0x17, 0x78, 0x51, 0x01, 0xF5, 0xA9, 0xEE, |
|
||||||
0x28, 0x1B, 0xDB, 0x68, 0xCE, 0xF3, 0x41, 0x6B, 0x29, 0x7F, 0xF0, 0xFF, 0x28, 0x7F, 0xCC, 0xC7, |
|
||||||
0x85, 0x34, 0x71, 0x31, 0x1A, 0xB3, 0x42, 0x96, 0x61, 0x18, 0xFF, 0x90, 0x93, 0xA4, 0xD4, 0x13, |
|
||||||
0x97, 0x7A, 0x5A, 0xF1, 0xB3, 0xB6, 0x53, 0x98, 0x8E, 0x31, 0xAA, 0xF8, 0xE3, 0xC8, 0xF6, 0xF0, |
|
||||||
0xF7, 0x3C, 0xF2, 0x65, 0x6D, 0x69, 0x5A, 0xA1, 0x31, 0x82, 0x3A, 0x57, 0x37, 0xCB, 0x7E, 0x9A, |
|
||||||
0xFD, 0xB7, 0xAD, 0xE8, 0xD1, 0xF1, 0xE9, 0x71, 0xFF, 0xB8, 0x5C, 0x38, 0x23, 0xE7, 0x25, 0x93, |
|
||||||
0x8A, 0x2B, 0x5D, 0xFA, 0xB2, 0x22, 0x80, 0x02, 0x1B, 0x45, 0x01, 0x7B, 0xDD, 0xDC, 0x54, 0x7E, |
|
||||||
0xF1, 0xB6, 0x77, 0x71, 0x6E, 0xC7, 0x24, 0x01, 0x8F, 0x24, 0x15, 0xE6, 0xC2, 0x82, 0x44, 0xF9, |
|
||||||
0xE0, 0xD7, 0xC7, 0xA5, 0x72, 0x5D, 0x7E, 0x61, 0x70, 0xC4, 0xDC, 0x52, 0xA7, 0xA9, 0x7E, 0x78, |
|
||||||
0xE2, 0x62, 0x5D, 0x99, 0xBF, 0x04, 0x41, 0x72, 0x1A, 0x2D, 0x13, 0x55, 0x11, 0x67, 0x46, 0xE5, |
|
||||||
0x30, 0x2F, 0xEE, 0xB2, 0x75, 0x0D, 0xD3, 0xC8, 0xB4, 0xC4, 0x84, 0xA5, 0xE5, 0x46, 0xA5, 0x12, |
|
||||||
0x14, 0xFE, 0xA2, 0xB6, 0xE7, 0x8B, 0x91, 0x24, 0xB7, 0x5A, 0x73, 0xAB, 0x6F, 0x41, 0x2A, 0x3E, |
|
||||||
0x58, 0x04, 0x23, 0x66, 0x39, 0xDB, 0x16, 0x77, 0xA3, 0x43, 0xEE, 0x61, 0x5C, 0x7F, 0xBA, 0x35, |
|
||||||
0x78, 0xD2, 0x3C, 0x79, 0x61, 0x9E, 0xFC, 0xB1, 0x7B, 0x2E, 0x1C, 0x45, 0xF9, 0xDA, 0xE2, 0x98, |
|
||||||
0xF6, 0x10, 0x58, 0xBB, 0x6D, 0x2F, 0x7D, 0x18, 0x20, 0xD2, 0x83, 0xCB, 0x00, 0xF4, 0x63, 0x58, |
|
||||||
0xFF, 0x4A, 0xEE, 0x88, 0x7A, 0x09, 0xAA, 0xA2, 0xAD, 0x73, 0x54, 0xD8, 0xEE, 0xFD, 0x81, 0xA3, |
|
||||||
0xF2, 0xCE, 0x65, 0x18, 0x48, 0x97, 0xC3, 0x92, 0x37, 0x8B, 0x75, 0xC1, 0x61, 0x19, 0x31, 0x64, |
|
||||||
0x6C, 0x00, 0xE3, 0xCD, 0x5D, 0x49, 0x13, 0xD5, 0x1C, 0xB4, 0xF0, 0x1B, 0x08, 0x8A, 0x4F, 0x39, |
|
||||||
0xCE, 0x9A, 0x38, 0xAD, 0x62, 0x72, 0xC5, 0x23, 0xC8, 0x4A, 0x67, 0x89, 0xC0, 0x6E, 0x10, 0x0D, |
|
||||||
0x0D, 0x7C, 0x64, 0x9A, 0xA1, 0xB6, 0x1D, 0x3E, 0x37, 0xD7, 0xBC, 0xD9, 0x54, 0xFA, 0x4B, 0x62, |
|
||||||
0x79, 0xD5, 0xB0, 0x8B, 0x1C, 0x56, 0xCC, 0x75, 0x7D, 0x1F, 0xF4, 0xA3, 0x4E, 0x29, 0xAF, 0x48, |
|
||||||
0xA4, 0x53, 0xD1, 0x83, 0xC4, 0x86, 0xA2, 0x41, 0xBE, 0x91, 0x40, 0x44, 0x72, 0x4A, 0x33, 0x5D, |
|
||||||
0xC7, 0xCA, 0xD2, 0x0B, 0x28, 0x49, 0x7A, 0xB2, 0x73, 0x95, 0x49, 0x6B, 0x25, 0x06, 0xFE, 0xC8, |
|
||||||
0xD7, 0xF0, 0xC7, 0xA1, 0xD0, 0xA3, 0x83, 0x9B, 0x49, 0x2B, 0x83, 0xA4, 0x23, 0x64, 0x83, 0xA9, |
|
||||||
0x37, 0xE4, 0xBB, 0xA8, 0x2D, 0x2F, 0xCB, 0xB4, 0x16, 0x50, 0x70, 0x71, 0x83, 0xBB, 0x11, 0x30, |
|
||||||
0x52, 0x5A, 0xC4, 0x9E, 0x94, 0xA8, 0xC7, 0x8F, 0x10, 0x1F, 0x53, 0x4A, 0x20, 0x06, 0x20, 0xA6, |
|
||||||
0x40, 0xD0, 0xA7, 0x42, 0x8A, 0x54, 0xE6, 0x92, 0x53, 0x2A, 0x20, 0xCA, 0x48, 0xCD, 0xE2, 0xC1, |
|
||||||
0x85, 0x78, 0xD4, 0x46, 0xD6, 0x80, 0xFD, 0xDC, 0xBD, 0x73, 0x33, 0xDE, 0x90, 0x68, 0x09, 0x56, |
|
||||||
0x36, 0x3D, 0x9A, 0xA6, 0x52, 0x5C, 0x54, 0xC7, 0x19, 0xF8, 0xA8, 0xA1, 0x03, 0x5A, 0x23, 0x84, |
|
||||||
0x11, 0x1E, 0x84, 0x8A, 0x01, 0x40, 0x7F, 0x42, 0xC3, 0x1C, 0x22, 0x70, 0x08, 0x20, 0x82, 0xA0, |
|
||||||
0x7F, 0x49, 0x0D, 0xF7, 0x64, 0x05, 0xC9, 0xF8, 0xD8, 0x6D, 0x35, 0xF0, 0x9D, 0x66, 0x95, 0xEC, |
|
||||||
0x20, 0xA5, 0xBD, 0x68, 0x24, 0xFA, 0x64, 0x98, 0x1A, 0x50, 0x00, 0xAC, 0xD9, 0x01, 0xA0, 0x1E, |
|
||||||
0x24, 0x5E, 0x63, 0x2B, 0x3F, 0xEF, 0x04, 0x2A, 0xBB, 0x00, 0xAB, 0xBB, 0x8E, 0x87, 0x5F, 0x39, |
|
||||||
0x4F, 0x19, 0xA7, 0x39, 0x26, 0x00, 0x7B, 0x93, 0x68, 0x7A, 0x99, 0x30, 0x2E, 0xCE, 0x64, 0x1B, |
|
||||||
0x6A, 0x6C, 0xB4, 0xE4, 0xF5, 0xA9, 0x87, 0x15, 0x79, 0x3F, 0xC5, 0x8B, 0xCB, 0x0C, 0xF3, 0xBA, |
|
||||||
0x53, 0x79, 0x77, 0xB1, 0x86, 0x70, 0x21, 0x50, 0x66, 0x38, 0xB3, 0x29, 0x74, 0xB0, 0xFA, 0xA1, |
|
||||||
0x48, 0x82, 0x7A, 0x4F, 0xB7, 0x42, 0xE2, 0xC1, 0x44, 0xED, 0x81, 0xF9, 0xDC, 0xC2, 0xD8, 0xE1, |
|
||||||
0x94, 0x83, 0x5A, 0x0A, 0xB5, 0x02, 0x45, 0xC6, 0x95, 0xCD, 0x98, 0x35, 0x1D, 0x6A, 0x58, 0x88, |
|
||||||
0x61, 0xE0, 0xAF, 0xFE, 0x05, 0x0F, 0x1E, 0x1C, 0xC8, 0x55, 0x3F, 0xE1, 0x23, 0xE3, 0x7E, 0xF4, |
|
||||||
0x23, 0x3E, 0x3E, 0xAF, 0xF0, 0xF1, 0x79, 0x1D, 0x1F, 0xB4, 0xAA, 0x3C, 0x98, 0x0C, 0x80, 0xEC, |
|
||||||
0x19, 0xE1, 0x64, 0x4C, 0x13, 0x58, 0xC0, 0x43, 0x50, 0x25, 0x7F, 0x8B, 0xB3, 0x84, 0xFE, 0x98, |
|
||||||
0xB3, 0xDE, 0x84, 0x8D, 0xC4, 0x23, 0xFE, 0x8A, 0xD5, 0xFF, 0x82, 0x4B, 0x3C, 0x70, 0x3D, 0x97, |
|
||||||
0x79, 0x6D, 0x5A, 0x49, 0x28, 0x3F, 0x7E, 0x2B, 0x91, 0x7E, 0xE4, 0x42, 0x78, 0xA9, 0x38, 0xC8, |
|
||||||
0xDF, 0xB7, 0xF4, 0x00, 0xBC, 0x11, 0xF8, 0x29, 0x35, 0x75, 0xBC, 0x0B, 0xA5, 0xFC, 0x29, 0x30, |
|
||||||
0x64, 0xA8, 0xC0, 0x47, 0xDD, 0xD9, 0xDC, 0x12, 0xAE, 0x01, 0x8A, 0xF1, 0xA3, 0x29, 0xB0, 0xEA, |
|
||||||
0xC9, 0x02, 0xD7, 0x9E, 0x40, 0x26, 0x04, 0x91, 0xE0, 0x48, 0xC8, 0xA7, 0x8D, 0x2F, 0x07, 0x9B, |
|
||||||
0x37, 0x35, 0xC8, 0x43, 0x2E, 0xFC, 0x98, 0x2E, 0x0C, 0x36, 0x6F, 0xFE, 0x6D, 0x36, 0xC6, 0xCC, |
|
||||||
0x5A, 0x76, 0xA4, 0x96, 0x4C, 0xF6, 0xF4, 0x0B, 0xBF, 0x71, 0x09, 0x48, 0x5D, 0x49, 0x78, 0x45, |
|
||||||
0x34, 0x03, 0x6B, 0x43, 0x61, 0xE1, 0x07, 0xFF, 0x47, 0x09, 0xF8, 0x91, 0x9E, 0x07, 0xCE, 0xBD, |
|
||||||
0xE6, 0x3D, 0x5E, 0x2F, 0x3E, 0x85, 0xE9, 0x56, 0xE9, 0xC1, 0x4A, 0xC7, 0xEF, 0x53, 0x3A, 0x76, |
|
||||||
0x59, 0xA2, 0x14, 0x4A, 0x14, 0x59, 0x88, 0x1A, 0x6A, 0x50, 0x0E, 0x51, 0x98, 0x89, 0x17, 0xCD, |
|
||||||
0x81, 0x02, 0x9B, 0x73, 0x34, 0x5B, 0x3A, 0x02, 0x0F, 0xF4, 0xF5, 0x45, 0xEE, 0xFC, 0x74, 0x76, |
|
||||||
0x7A, 0x22, 0x44, 0x7C, 0xA5, 0x62, 0x22, 0xD0, 0xAA, 0x2E, 0x2C, 0x2F, 0xCF, 0x9C, 0x89, 0xE4, |
|
||||||
0xA1, 0x28, 0x75, 0x30, 0x31, 0x28, 0x87, 0xFE, 0x74, 0x31, 0xFC, 0x0A, 0x71, 0xD6, 0xD0, 0xCF, |
|
||||||
0x52, 0x48, 0x58, 0x5B, 0x36, 0xA2, 0xF7, 0xFB, 0x97, 0xF6, 0xAE, 0xDD, 0x84, 0xBA, 0x00, 0xB4, |
|
||||||
0x0A, 0x69, 0x19, 0xEE, 0x7D, 0xFE, 0xB7, 0x90, 0xB7, 0xFF, 0x1E, 0x32, 0x83, 0xA8, 0x95, 0x42, |
|
||||||
0x58, 0x2A, 0xF0, 0xAB, 0xB8, 0x93, 0x24, 0x9A, 0x4A, 0xB4, 0xE3, 0x24, 0xC1, 0x4B, 0xE9, 0x43, |
|
||||||
0x85, 0xA2, 0x0D, 0x61, 0x31, 0xA5, 0x89, 0xE6, 0x47, 0x34, 0xD5, 0x78, 0x24, 0xB4, 0x34, 0x8B, |
|
||||||
0x63, 0x68, 0x5C, 0x56, 0xF4, 0x61, 0xEB, 0xC5, 0xEB, 0xCB, 0xFB, 0x8C, 0x66, 0xD4, 0xCF, 0x97, |
|
||||||
0x69, 0x52, 0xD1, 0x0B, 0x56, 0x50, 0xDF, 0x10, 0xEE, 0x7E, 0xB9, 0xC9, 0xEB, 0xA9, 0x8C, 0x73, |
|
||||||
0x8C, 0xA2, 0x1B, 0x2D, 0x35, 0x07, 0xE9, 0x26, 0x40, 0xD5, 0xE5, 0x59, 0x10, 0xCC, 0xDB, 0x2B, |
|
||||||
0xB4, 0xA0, 0xF1, 0x8A, 0x44, 0x24, 0x9F, 0xCB, 0x67, 0x7F, 0xE4, 0xC9, 0xA9, 0xE2, 0x82, 0x50, |
|
||||||
0xF2, 0x54, 0xA9, 0x36, 0xAD, 0x0D, 0x63, 0x83, 0x6A, 0x8C, 0xA7, 0x82, 0x70, 0x0F, 0xAF, 0x51, |
|
||||||
0xE9, 0xC2, 0x2C, 0x6A, 0x29, 0xDC, 0xDE, 0x46, 0x5F, 0xCB, 0x6D, 0xE9, 0x89, 0x7C, 0x2A, 0x25, |
|
||||||
0xE3, 0xAE, 0xAE, 0x63, 0x55, 0x45, 0xB1, 0x3E, 0x25, 0x61, 0x5A, 0x26, 0x5B, 0x54, 0x06, 0x26, |
|
||||||
0x77, 0x0B, 0x70, 0x9B, 0x06, 0x29, 0x1C, 0xBD, 0x7E, 0x7F, 0xCE, 0x46, 0xD1, 0xCE, 0x11, 0x80, |
|
||||||
0x69, 0xC5, 0x3E, 0x93, 0xD7, 0xE0, 0x24, 0xCC, 0x73, 0x07, 0x32, 0xE9, 0x4A, 0x03, 0x0E, 0xA9, |
|
||||||
0x98, 0x44, 0xFE, 0x81, 0x7E, 0xA0, 0x3B, 0x3A, 0xFC, 0xBB, 0x09, 0x35, 0x47, 0xCD, 0xA5, 0xD0, |
|
||||||
0xA4, 0xFA, 0x74, 0x70, 0xF5, 0x06, 0xC2, 0x53, 0x0C, 0xA5, 0x01, 0x17, 0x50, 0x34, 0xD7, 0x74, |
|
||||||
0x7C, 0x7A, 0x7D, 0x0C, 0x29, 0xC8, 0x7F, 0x21, 0x37, 0x66, 0xBB, 0xAA, 0x6C, 0xB8, 0xF3, 0xEA, |
|
||||||
0x75, 0x56, 0x2E, 0x03, 0x7A, 0x61, 0x8C, 0x58, 0x0F, 0x29, 0x7E, 0xFB, 0x7B, 0xF4, 0x9E, 0x8D, |
|
||||||
0x15, 0xD2, 0x6A, 0x5D, 0x6F, 0xCE, 0x76, 0x90, 0x67, 0x89, 0xD5, 0x43, 0x2C, 0x70, 0x97, 0x1F, |
|
||||||
0x29, 0x59, 0x95, 0x35, 0xDC, 0xF6, 0x48, 0x10, 0xE0, 0xC7, 0x5A, 0x03, 0x1B, 0x6A, 0x22, 0xB2, |
|
||||||
0xD4, 0x42, 0x22, 0x29, 0x08, 0x90, 0xD2, 0x3E, 0x84, 0x39, 0xD3, 0x92, 0x65, 0x86, 0xB2, 0xA1, |
|
||||||
0xBC, 0xFF, 0xC5, 0x9A, 0xA3, 0x64, 0x46, 0xE8, 0xCE, 0xF9, 0x6C, 0x73, 0x53, 0xD8, 0x85, 0x99, |
|
||||||
0x18, 0x05, 0x52, 0x8A, 0x01, 0x1C, 0x9A, 0x7D, 0x68, 0x2D, 0x8C, 0xB2, 0x90, 0x58, 0xAB, 0x3D, |
|
||||||
0xD2, 0xB6, 0x51, 0x55, 0x03, 0x54, 0x7C, 0x46, 0x01, 0x03, 0xCE, 0xB2, 0x24, 0x80, 0xA8, 0x8B, |
|
||||||
0x39, 0xBA, 0xB2, 0x2D, 0xC5, 0xBA, 0xD0, 0x84, 0x0E, 0xEC, 0x67, 0xC8, 0x12, 0x95, 0x97, 0xAD, |
|
||||||
0xA2, 0x27, 0x12, 0xC5, 0x77, 0x95, 0x9E, 0xC8, 0x6F, 0xE5, 0x84, 0xAA, 0xC8, 0x77, 0x88, 0x2F, |
|
||||||
0x13, 0x5C, 0xD4, 0xD1, 0x13, 0xA0, 0x24, 0x83, 0x52, 0x34, 0x60, 0x2A, 0x2C, 0x37, 0xEE, 0xEB, |
|
||||||
0xD3, 0xE9, 0xB4, 0x8E, 0xDF, 0x6A, 0xEB, 0x70, 0x82, 0xB2, 0x02, 0x5F, 0x5F, 0xC7, 0x21, 0x47, |
|
||||||
0x15, 0x58, 0xF8, 0x6E, 0xE1, 0xAC, 0xBA, 0xE8, 0x42, 0x7F, 0x2B, 0xDE, 0xD4, 0xAA, 0xD2, 0x59, |
|
||||||
0xE1, 0x73, 0x79, 0xDB, 0x7B, 0x3B, 0x2B, 0x20, 0x32, 0xC4, 0xAF, 0xB2, 0x90, 0x69, 0x20, 0x0D, |
|
||||||
0x3B, 0xE5, 0x46, 0x56, 0x25, 0x85, 0x65, 0x5C, 0xB0, 0xE3, 0x2C, 0x9D, 0x18, 0x33, 0x60, 0xDD, |
|
||||||
0x11, 0x96, 0xD2, 0x95, 0x43, 0x2D, 0x65, 0xB7, 0x0E, 0xB7, 0x0A, 0xFB, 0x70, 0x30, 0x83, 0x94, |
|
||||||
0x79, 0xFB, 0xF3, 0x4F, 0x39, 0x5B, 0xDE, 0xF6, 0x92, 0x62, 0x71, 0xE1, 0xF3, 0xFC, 0xA9, 0x35, |
|
||||||
0xAF, 0x69, 0xA5, 0xD1, 0xAF, 0xC4, 0x97, 0xBD, 0x46, 0xFE, 0x19, 0x3B, 0xFF, 0x9C, 0xAD, 0x81, |
|
||||||
0xB1, 0x43, 0x23, 0x2A, 0xDC, 0x4C, 0x8C, 0xEA, 0x2F, 0x34, 0xE6, 0x63, 0x79, 0x29, 0xBF, 0x2D, |
|
||||||
0xA0, 0x54, 0xA9, 0xD3, 0x68, 0x78, 0x3E, 0xFF, 0x9A, 0x42, 0x19, 0x1D, 0x65, 0xFE, 0x28, 0x20, |
|
||||||
0x09, 0xC5, 0x82, 0xA3, 0x41, 0xBE, 0x92, 0xFB, 0x46, 0xC0, 0x86, 0x69, 0x03, 0x93, 0x6D, 0xCB, |
|
||||||
0xDE, 0xB2, 0x77, 0x71, 0x64, 0x7F, 0x4D, 0xF7, 0x57, 0x4F, 0xD8, 0x5F, 0x34, 0x69, 0x58, 0x0B, |
|
||||||
0xE7, 0xB5, 0xAB, 0x8A, 0x4D, 0x6A, 0x83, 0xFB, 0xC4, 0xA7, 0x70, 0x3D, 0x6F, 0xB3, 0xCC, 0xB6, |
|
||||||
0x1A, 0xE4, 0x5F, 0x60, 0xD4, 0x31, 0xBA, 0x95, 0x2F, 0x92, 0xF4, 0x81, 0x7B, 0x18, 0x5B, 0x17, |
|
||||||
0x54, 0x26, 0x70, 0x49, 0xD5, 0x87, 0x34, 0xB9, 0xD3, 0x9C, 0x2F, 0x39, 0xC3, 0xB7, 0x3C, 0xA8, |
|
||||||
0x03, 0xE4, 0x37, 0x9C, 0x72, 0x39, 0xB0, 0xBF, 0x07, 0x5D, 0x33, 0x2A, 0x41, 0x79, 0xB1, 0x26, |
|
||||||
0x9B, 0xE6, 0x7C, 0x02, 0x82, 0x01, 0x70, 0xB1, 0xA3, 0x48, 0xCD, 0x2B, 0xCB, 0x98, 0x9B, 0x57, |
|
||||||
0x96, 0x54, 0xE2, 0x5F, 0x59, 0xCC, 0xDB, 0x9F, 0xFC, 0xDB, 0x4C, 0xF9, 0x7F, 0x5B, 0x28, 0x36, |
|
||||||
0x32, 0xF9, 0xE1, 0x09, 0xF7, 0x56, 0x3F, 0x45, 0xAD, 0x47, 0x51, 0xBB, 0xF7, 0xFF, 0x17, 0x53, |
|
||||||
0xE8, 0x9D, 0x36, 0x92, 0x29, 0x00, 0x00 |
|
||||||
}; |
|
||||||
|
|
||||||
#define SPIFFS_MAXLENGTH_FILEPATH 32 |
|
||||||
const char *excludeListFile = "/.exclude.files"; |
|
||||||
|
|
||||||
typedef struct ExcludeListS { |
|
||||||
char *item; |
|
||||||
ExcludeListS *next; |
|
||||||
} ExcludeList; |
|
||||||
|
|
||||||
static ExcludeList *excludes = NULL; |
|
||||||
|
|
||||||
static bool matchWild(const char *pattern, const char *testee) { |
|
||||||
const char *nxPat = NULL, *nxTst = NULL; |
|
||||||
|
|
||||||
while (*testee) { |
|
||||||
if (( *pattern == '?' ) || (*pattern == *testee)){ |
|
||||||
pattern++;testee++; |
|
||||||
continue; |
|
||||||
} |
|
||||||
if (*pattern=='*'){ |
|
||||||
nxPat=pattern++; nxTst=testee; |
|
||||||
continue; |
|
||||||
} |
|
||||||
if (nxPat){
|
|
||||||
pattern = nxPat+1; testee=++nxTst; |
|
||||||
continue; |
|
||||||
} |
|
||||||
return false; |
|
||||||
} |
|
||||||
while (*pattern=='*'){pattern++;}
|
|
||||||
return (*pattern == 0); |
|
||||||
} |
|
||||||
|
|
||||||
static bool addExclude(const char *item){ |
|
||||||
size_t len = strlen(item); |
|
||||||
if(!len){ |
|
||||||
return false; |
|
||||||
} |
|
||||||
ExcludeList *e = (ExcludeList *)malloc(sizeof(ExcludeList)); |
|
||||||
if(!e){ |
|
||||||
return false; |
|
||||||
} |
|
||||||
e->item = (char *)malloc(len+1); |
|
||||||
if(!e->item){ |
|
||||||
free(e); |
|
||||||
return false; |
|
||||||
} |
|
||||||
memcpy(e->item, item, len+1); |
|
||||||
e->next = excludes; |
|
||||||
excludes = e; |
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
static void loadExcludeList(fs::FS &_fs, const char *filename){ |
|
||||||
static char linebuf[SPIFFS_MAXLENGTH_FILEPATH]; |
|
||||||
fs::File excludeFile=_fs.open(filename, "r"); |
|
||||||
if(!excludeFile){ |
|
||||||
//addExclude("/*.js.gz");
|
|
||||||
return; |
|
||||||
} |
|
||||||
#ifdef ESP32 |
|
||||||
if(excludeFile.isDirectory()){ |
|
||||||
excludeFile.close(); |
|
||||||
return; |
|
||||||
} |
|
||||||
#endif |
|
||||||
if (excludeFile.size() > 0){ |
|
||||||
uint8_t idx; |
|
||||||
bool isOverflowed = false; |
|
||||||
while (excludeFile.available()){ |
|
||||||
linebuf[0] = '\0'; |
|
||||||
idx = 0; |
|
||||||
int lastChar; |
|
||||||
do { |
|
||||||
lastChar = excludeFile.read(); |
|
||||||
if(lastChar != '\r'){ |
|
||||||
linebuf[idx++] = (char) lastChar; |
|
||||||
} |
|
||||||
} while ((lastChar >= 0) && (lastChar != '\n') && (idx < SPIFFS_MAXLENGTH_FILEPATH)); |
|
||||||
|
|
||||||
if(isOverflowed){ |
|
||||||
isOverflowed = (lastChar != '\n'); |
|
||||||
continue; |
|
||||||
} |
|
||||||
isOverflowed = (idx >= SPIFFS_MAXLENGTH_FILEPATH); |
|
||||||
linebuf[idx-1] = '\0'; |
|
||||||
if(!addExclude(linebuf)){ |
|
||||||
excludeFile.close(); |
|
||||||
return; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
excludeFile.close(); |
|
||||||
} |
|
||||||
|
|
||||||
static bool isExcluded(fs::FS &_fs, const char *filename) { |
|
||||||
if(excludes == NULL){ |
|
||||||
loadExcludeList(_fs, excludeListFile); |
|
||||||
} |
|
||||||
ExcludeList *e = excludes; |
|
||||||
while(e){ |
|
||||||
if (matchWild(e->item, filename)){ |
|
||||||
return true; |
|
||||||
} |
|
||||||
e = e->next; |
|
||||||
} |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
// WEB HANDLER IMPLEMENTATION
|
|
||||||
|
|
||||||
#ifdef ESP32 |
|
||||||
SPIFFSEditor::SPIFFSEditor(const fs::FS& fs, const String& username, const String& password) |
|
||||||
#else |
|
||||||
SPIFFSEditor::SPIFFSEditor(const String& username, const String& password, const fs::FS& fs) |
|
||||||
#endif |
|
||||||
:_fs(fs) |
|
||||||
,_username(username) |
|
||||||
,_password(password) |
|
||||||
,_authenticated(false) |
|
||||||
,_startTime(0) |
|
||||||
{} |
|
||||||
|
|
||||||
bool SPIFFSEditor::canHandle(AsyncWebServerRequest *request){ |
|
||||||
if(request->url().equalsIgnoreCase("/edit")){ |
|
||||||
if(request->method() == HTTP_GET){ |
|
||||||
if(request->hasParam("list")) |
|
||||||
return true; |
|
||||||
if(request->hasParam("edit")){ |
|
||||||
request->_tempFile = _fs.open(request->arg("edit"), "r"); |
|
||||||
if(!request->_tempFile){ |
|
||||||
return false; |
|
||||||
} |
|
||||||
#ifdef ESP32 |
|
||||||
if(request->_tempFile.isDirectory()){ |
|
||||||
request->_tempFile.close(); |
|
||||||
return false; |
|
||||||
} |
|
||||||
#endif |
|
||||||
} |
|
||||||
if(request->hasParam("download")){ |
|
||||||
request->_tempFile = _fs.open(request->arg("download"), "r"); |
|
||||||
if(!request->_tempFile){ |
|
||||||
return false; |
|
||||||
} |
|
||||||
#ifdef ESP32 |
|
||||||
if(request->_tempFile.isDirectory()){ |
|
||||||
request->_tempFile.close(); |
|
||||||
return false; |
|
||||||
} |
|
||||||
#endif |
|
||||||
} |
|
||||||
request->addInterestingHeader("If-Modified-Since"); |
|
||||||
return true; |
|
||||||
} |
|
||||||
else if(request->method() == HTTP_POST) |
|
||||||
return true; |
|
||||||
else if(request->method() == HTTP_DELETE) |
|
||||||
return true; |
|
||||||
else if(request->method() == HTTP_PUT) |
|
||||||
return true; |
|
||||||
|
|
||||||
} |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
void SPIFFSEditor::handleRequest(AsyncWebServerRequest *request){ |
|
||||||
if(_username.length() && _password.length() && !request->authenticate(_username.c_str(), _password.c_str())) |
|
||||||
return request->requestAuthentication(); |
|
||||||
|
|
||||||
if(request->method() == HTTP_GET){ |
|
||||||
if(request->hasParam("list")){ |
|
||||||
String path = request->getParam("list")->value(); |
|
||||||
#ifdef ESP32 |
|
||||||
File dir = _fs.open(path); |
|
||||||
#else |
|
||||||
Dir dir = _fs.openDir(path); |
|
||||||
#endif |
|
||||||
path = String(); |
|
||||||
String output = "["; |
|
||||||
#ifdef ESP32 |
|
||||||
File entry = dir.openNextFile(); |
|
||||||
while(entry){ |
|
||||||
#else |
|
||||||
while(dir.next()){ |
|
||||||
fs::File entry = dir.openFile("r"); |
|
||||||
#endif |
|
||||||
if (isExcluded(_fs, entry.name())) { |
|
||||||
#ifdef ESP32 |
|
||||||
entry = dir.openNextFile(); |
|
||||||
#endif |
|
||||||
continue; |
|
||||||
} |
|
||||||
if (output != "[") output += ','; |
|
||||||
output += "{\"type\":\""; |
|
||||||
output += "file"; |
|
||||||
output += "\",\"name\":\""; |
|
||||||
output += String(entry.name()); |
|
||||||
output += "\",\"size\":"; |
|
||||||
output += String(entry.size()); |
|
||||||
output += "}"; |
|
||||||
#ifdef ESP32 |
|
||||||
entry = dir.openNextFile(); |
|
||||||
#else |
|
||||||
entry.close(); |
|
||||||
#endif |
|
||||||
} |
|
||||||
#ifdef ESP32 |
|
||||||
dir.close(); |
|
||||||
#endif |
|
||||||
output += "]"; |
|
||||||
request->send(200, "application/json", output); |
|
||||||
output = String(); |
|
||||||
} |
|
||||||
else if(request->hasParam("edit") || request->hasParam("download")){ |
|
||||||
request->send(request->_tempFile, request->_tempFile.name(), String(), request->hasParam("download")); |
|
||||||
} |
|
||||||
else { |
|
||||||
const char * buildTime = __DATE__ " " __TIME__ " GMT"; |
|
||||||
if (request->header("If-Modified-Since").equals(buildTime)) { |
|
||||||
request->send(304); |
|
||||||
} else { |
|
||||||
AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", edit_htm_gz, edit_htm_gz_len); |
|
||||||
response->addHeader("Content-Encoding", "gzip"); |
|
||||||
response->addHeader("Last-Modified", buildTime); |
|
||||||
request->send(response); |
|
||||||
} |
|
||||||
} |
|
||||||
} else if(request->method() == HTTP_DELETE){ |
|
||||||
if(request->hasParam("path", true)){ |
|
||||||
_fs.remove(request->getParam("path", true)->value()); |
|
||||||
request->send(200, "", "DELETE: "+request->getParam("path", true)->value()); |
|
||||||
} else |
|
||||||
request->send(404); |
|
||||||
} else if(request->method() == HTTP_POST){ |
|
||||||
if(request->hasParam("data", true, true) && _fs.exists(request->getParam("data", true, true)->value())) |
|
||||||
request->send(200, "", "UPLOADED: "+request->getParam("data", true, true)->value()); |
|
||||||
else |
|
||||||
request->send(500); |
|
||||||
} else if(request->method() == HTTP_PUT){ |
|
||||||
if(request->hasParam("path", true)){ |
|
||||||
String filename = request->getParam("path", true)->value(); |
|
||||||
if(_fs.exists(filename)){ |
|
||||||
request->send(200); |
|
||||||
} else { |
|
||||||
fs::File f = _fs.open(filename, "w"); |
|
||||||
if(f){ |
|
||||||
f.write((uint8_t)0x00); |
|
||||||
f.close(); |
|
||||||
request->send(200, "", "CREATE: "+filename); |
|
||||||
} else { |
|
||||||
request->send(500); |
|
||||||
} |
|
||||||
} |
|
||||||
} else |
|
||||||
request->send(400); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
void SPIFFSEditor::handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final){ |
|
||||||
if(!index){ |
|
||||||
if(!_username.length() || request->authenticate(_username.c_str(),_password.c_str())){ |
|
||||||
_authenticated = true; |
|
||||||
request->_tempFile = _fs.open(filename, "w"); |
|
||||||
_startTime = millis(); |
|
||||||
} |
|
||||||
} |
|
||||||
if(_authenticated && request->_tempFile){ |
|
||||||
if(len){ |
|
||||||
request->_tempFile.write(data,len); |
|
||||||
} |
|
||||||
if(final){ |
|
||||||
request->_tempFile.close(); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,24 +0,0 @@ |
|||||||
#ifndef SPIFFSEditor_H_ |
|
||||||
#define SPIFFSEditor_H_ |
|
||||||
#include <ESPAsyncWebServer.h> |
|
||||||
|
|
||||||
class SPIFFSEditor: public AsyncWebHandler { |
|
||||||
private: |
|
||||||
fs::FS _fs; |
|
||||||
String _username; |
|
||||||
String _password;
|
|
||||||
bool _authenticated; |
|
||||||
uint32_t _startTime; |
|
||||||
public: |
|
||||||
#ifdef ESP32 |
|
||||||
SPIFFSEditor(const fs::FS& fs, const String& username=String(), const String& password=String()); |
|
||||||
#else |
|
||||||
SPIFFSEditor(const String& username=String(), const String& password=String(), const fs::FS& fs=SPIFFS); |
|
||||||
#endif |
|
||||||
virtual bool canHandle(AsyncWebServerRequest *request) override final; |
|
||||||
virtual void handleRequest(AsyncWebServerRequest *request) override final; |
|
||||||
virtual void handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) override final; |
|
||||||
virtual bool isRequestHandlerTrivial() override final {return false;} |
|
||||||
}; |
|
||||||
|
|
||||||
#endif |
|
@ -1,193 +0,0 @@ |
|||||||
/*
|
|
||||||
Asynchronous WebServer library for Espressif MCUs |
|
||||||
|
|
||||||
Copyright (c) 2016 Hristo Gochkov. All rights reserved. |
|
||||||
This file is part of the esp8266 core for Arduino environment. |
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or |
|
||||||
modify it under the terms of the GNU Lesser General Public |
|
||||||
License as published by the Free Software Foundation; either |
|
||||||
version 2.1 of the License, or (at your option) any later version. |
|
||||||
|
|
||||||
This library is distributed in the hope that it will be useful, |
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
||||||
Lesser General Public License for more details. |
|
||||||
|
|
||||||
You should have received a copy of the GNU Lesser General Public |
|
||||||
License along with this library; if not, write to the Free Software |
|
||||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
|
||||||
*/ |
|
||||||
#ifndef STRINGARRAY_H_ |
|
||||||
#define STRINGARRAY_H_ |
|
||||||
|
|
||||||
#include "stddef.h" |
|
||||||
#include "WString.h" |
|
||||||
|
|
||||||
template <typename T> |
|
||||||
class LinkedListNode { |
|
||||||
T _value; |
|
||||||
public: |
|
||||||
LinkedListNode<T>* next; |
|
||||||
LinkedListNode(const T val): _value(val), next(nullptr) {} |
|
||||||
~LinkedListNode(){} |
|
||||||
const T& value() const { return _value; }; |
|
||||||
T& value(){ return _value; } |
|
||||||
}; |
|
||||||
|
|
||||||
template <typename T, template<typename> class Item = LinkedListNode> |
|
||||||
class LinkedList { |
|
||||||
public: |
|
||||||
typedef Item<T> ItemType; |
|
||||||
typedef std::function<void(const T&)> OnRemove; |
|
||||||
typedef std::function<bool(const T&)> Predicate; |
|
||||||
private: |
|
||||||
ItemType* _root; |
|
||||||
OnRemove _onRemove; |
|
||||||
|
|
||||||
class Iterator { |
|
||||||
ItemType* _node; |
|
||||||
public: |
|
||||||
Iterator(ItemType* current = nullptr) : _node(current) {} |
|
||||||
Iterator(const Iterator& i) : _node(i._node) {} |
|
||||||
Iterator& operator ++() { _node = _node->next; return *this; } |
|
||||||
bool operator != (const Iterator& i) const { return _node != i._node; } |
|
||||||
const T& operator * () const { return _node->value(); } |
|
||||||
const T* operator -> () const { return &_node->value(); } |
|
||||||
}; |
|
||||||
|
|
||||||
public: |
|
||||||
typedef const Iterator ConstIterator; |
|
||||||
ConstIterator begin() const { return ConstIterator(_root); } |
|
||||||
ConstIterator end() const { return ConstIterator(nullptr); } |
|
||||||
|
|
||||||
LinkedList(OnRemove onRemove) : _root(nullptr), _onRemove(onRemove) {} |
|
||||||
~LinkedList(){} |
|
||||||
void add(const T& t){ |
|
||||||
auto it = new ItemType(t); |
|
||||||
if(!_root){ |
|
||||||
_root = it; |
|
||||||
} else { |
|
||||||
auto i = _root; |
|
||||||
while(i->next) i = i->next; |
|
||||||
i->next = it; |
|
||||||
} |
|
||||||
} |
|
||||||
T& front() const { |
|
||||||
return _root->value(); |
|
||||||
} |
|
||||||
|
|
||||||
bool isEmpty() const { |
|
||||||
return _root == nullptr; |
|
||||||
} |
|
||||||
size_t length() const { |
|
||||||
size_t i = 0; |
|
||||||
auto it = _root; |
|
||||||
while(it){ |
|
||||||
i++; |
|
||||||
it = it->next; |
|
||||||
} |
|
||||||
return i; |
|
||||||
} |
|
||||||
size_t count_if(Predicate predicate) const { |
|
||||||
size_t i = 0; |
|
||||||
auto it = _root; |
|
||||||
while(it){ |
|
||||||
if (!predicate){ |
|
||||||
i++; |
|
||||||
} |
|
||||||
else if (predicate(it->value())) { |
|
||||||
i++; |
|
||||||
} |
|
||||||
it = it->next; |
|
||||||
} |
|
||||||
return i; |
|
||||||
} |
|
||||||
const T* nth(size_t N) const { |
|
||||||
size_t i = 0; |
|
||||||
auto it = _root; |
|
||||||
while(it){ |
|
||||||
if(i++ == N) |
|
||||||
return &(it->value()); |
|
||||||
it = it->next; |
|
||||||
} |
|
||||||
return nullptr; |
|
||||||
} |
|
||||||
bool remove(const T& t){ |
|
||||||
auto it = _root; |
|
||||||
auto pit = _root; |
|
||||||
while(it){ |
|
||||||
if(it->value() == t){ |
|
||||||
if(it == _root){ |
|
||||||
_root = _root->next; |
|
||||||
} else { |
|
||||||
pit->next = it->next; |
|
||||||
} |
|
||||||
|
|
||||||
if (_onRemove) { |
|
||||||
_onRemove(it->value()); |
|
||||||
} |
|
||||||
|
|
||||||
delete it; |
|
||||||
return true; |
|
||||||
} |
|
||||||
pit = it; |
|
||||||
it = it->next; |
|
||||||
} |
|
||||||
return false; |
|
||||||
} |
|
||||||
bool remove_first(Predicate predicate){ |
|
||||||
auto it = _root; |
|
||||||
auto pit = _root; |
|
||||||
while(it){ |
|
||||||
if(predicate(it->value())){ |
|
||||||
if(it == _root){ |
|
||||||
_root = _root->next; |
|
||||||
} else { |
|
||||||
pit->next = it->next; |
|
||||||
} |
|
||||||
if (_onRemove) { |
|
||||||
_onRemove(it->value()); |
|
||||||
} |
|
||||||
delete it; |
|
||||||
return true; |
|
||||||
} |
|
||||||
pit = it; |
|
||||||
it = it->next; |
|
||||||
} |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
void free(){ |
|
||||||
while(_root != nullptr){ |
|
||||||
auto it = _root; |
|
||||||
_root = _root->next; |
|
||||||
if (_onRemove) { |
|
||||||
_onRemove(it->value()); |
|
||||||
} |
|
||||||
delete it; |
|
||||||
} |
|
||||||
_root = nullptr; |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
|
|
||||||
class StringArray : public LinkedList<String> { |
|
||||||
public: |
|
||||||
|
|
||||||
StringArray() : LinkedList(nullptr) {} |
|
||||||
|
|
||||||
bool containsIgnoreCase(const String& str){ |
|
||||||
for (const auto& s : *this) { |
|
||||||
if (str.equalsIgnoreCase(s)) { |
|
||||||
return true; |
|
||||||
} |
|
||||||
} |
|
||||||
return false; |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#endif /* STRINGARRAY_H_ */ |
|
@ -1,235 +0,0 @@ |
|||||||
/*
|
|
||||||
Asynchronous WebServer library for Espressif MCUs |
|
||||||
|
|
||||||
Copyright (c) 2016 Hristo Gochkov. All rights reserved. |
|
||||||
This file is part of the esp8266 core for Arduino environment. |
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or |
|
||||||
modify it under the terms of the GNU Lesser General Public |
|
||||||
License as published by the Free Software Foundation; either |
|
||||||
version 2.1 of the License, or (at your option) any later version. |
|
||||||
|
|
||||||
This library is distributed in the hope that it will be useful, |
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
||||||
Lesser General Public License for more details. |
|
||||||
|
|
||||||
You should have received a copy of the GNU Lesser General Public |
|
||||||
License along with this library; if not, write to the Free Software |
|
||||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
|
||||||
*/ |
|
||||||
#include "WebAuthentication.h" |
|
||||||
#include <libb64/cencode.h> |
|
||||||
#ifdef ESP32 |
|
||||||
#include "mbedtls/md5.h" |
|
||||||
#else |
|
||||||
#include "md5.h" |
|
||||||
#endif |
|
||||||
|
|
||||||
|
|
||||||
// Basic Auth hash = base64("username:password")
|
|
||||||
|
|
||||||
bool checkBasicAuthentication(const char * hash, const char * username, const char * password){ |
|
||||||
if(username == NULL || password == NULL || hash == NULL) |
|
||||||
return false; |
|
||||||
|
|
||||||
size_t toencodeLen = strlen(username)+strlen(password)+1; |
|
||||||
size_t encodedLen = base64_encode_expected_len(toencodeLen); |
|
||||||
if(strlen(hash) != encodedLen) |
|
||||||
return false; |
|
||||||
|
|
||||||
char *toencode = new char[toencodeLen+1]; |
|
||||||
if(toencode == NULL){ |
|
||||||
return false; |
|
||||||
} |
|
||||||
char *encoded = new char[base64_encode_expected_len(toencodeLen)+1]; |
|
||||||
if(encoded == NULL){ |
|
||||||
delete[] toencode; |
|
||||||
return false; |
|
||||||
} |
|
||||||
sprintf(toencode, "%s:%s", username, password); |
|
||||||
if(base64_encode_chars(toencode, toencodeLen, encoded) > 0 && memcmp(hash, encoded, encodedLen) == 0){ |
|
||||||
delete[] toencode; |
|
||||||
delete[] encoded; |
|
||||||
return true; |
|
||||||
} |
|
||||||
delete[] toencode; |
|
||||||
delete[] encoded; |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
static bool getMD5(uint8_t * data, uint16_t len, char * output){//33 bytes or more
|
|
||||||
#ifdef ESP32 |
|
||||||
mbedtls_md5_context _ctx; |
|
||||||
#else |
|
||||||
md5_context_t _ctx; |
|
||||||
#endif |
|
||||||
uint8_t i; |
|
||||||
uint8_t * _buf = (uint8_t*)malloc(16); |
|
||||||
if(_buf == NULL) |
|
||||||
return false; |
|
||||||
memset(_buf, 0x00, 16); |
|
||||||
#ifdef ESP32 |
|
||||||
mbedtls_md5_init(&_ctx); |
|
||||||
mbedtls_md5_starts_ret(&_ctx); |
|
||||||
mbedtls_md5_update_ret(&_ctx, data, len); |
|
||||||
mbedtls_md5_finish_ret(&_ctx, _buf); |
|
||||||
#else |
|
||||||
MD5Init(&_ctx); |
|
||||||
MD5Update(&_ctx, data, len); |
|
||||||
MD5Final(_buf, &_ctx); |
|
||||||
#endif |
|
||||||
for(i = 0; i < 16; i++) { |
|
||||||
sprintf(output + (i * 2), "%02x", _buf[i]); |
|
||||||
} |
|
||||||
free(_buf); |
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
static String genRandomMD5(){ |
|
||||||
#ifdef ESP8266 |
|
||||||
uint32_t r = RANDOM_REG32; |
|
||||||
#else |
|
||||||
uint32_t r = rand(); |
|
||||||
#endif |
|
||||||
char * out = (char*)malloc(33); |
|
||||||
if(out == NULL || !getMD5((uint8_t*)(&r), 4, out)) |
|
||||||
return ""; |
|
||||||
String res = String(out); |
|
||||||
free(out); |
|
||||||
return res; |
|
||||||
} |
|
||||||
|
|
||||||
static String stringMD5(const String& in){ |
|
||||||
char * out = (char*)malloc(33); |
|
||||||
if(out == NULL || !getMD5((uint8_t*)(in.c_str()), in.length(), out)) |
|
||||||
return ""; |
|
||||||
String res = String(out); |
|
||||||
free(out); |
|
||||||
return res; |
|
||||||
} |
|
||||||
|
|
||||||
String generateDigestHash(const char * username, const char * password, const char * realm){ |
|
||||||
if(username == NULL || password == NULL || realm == NULL){ |
|
||||||
return ""; |
|
||||||
} |
|
||||||
char * out = (char*)malloc(33); |
|
||||||
String res = String(username); |
|
||||||
res.concat(":"); |
|
||||||
res.concat(realm); |
|
||||||
res.concat(":"); |
|
||||||
String in = res; |
|
||||||
in.concat(password); |
|
||||||
if(out == NULL || !getMD5((uint8_t*)(in.c_str()), in.length(), out)) |
|
||||||
return ""; |
|
||||||
res.concat(out); |
|
||||||
free(out); |
|
||||||
return res; |
|
||||||
} |
|
||||||
|
|
||||||
String requestDigestAuthentication(const char * realm){ |
|
||||||
String header = "realm=\""; |
|
||||||
if(realm == NULL) |
|
||||||
header.concat("asyncesp"); |
|
||||||
else |
|
||||||
header.concat(realm); |
|
||||||
header.concat( "\", qop=\"auth\", nonce=\""); |
|
||||||
header.concat(genRandomMD5()); |
|
||||||
header.concat("\", opaque=\""); |
|
||||||
header.concat(genRandomMD5()); |
|
||||||
header.concat("\""); |
|
||||||
return header; |
|
||||||
} |
|
||||||
|
|
||||||
bool checkDigestAuthentication(const char * header, const char * method, const char * username, const char * password, const char * realm, bool passwordIsHash, const char * nonce, const char * opaque, const char * uri){ |
|
||||||
if(username == NULL || password == NULL || header == NULL || method == NULL){ |
|
||||||
//os_printf("AUTH FAIL: missing requred fields\n");
|
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
String myHeader = String(header); |
|
||||||
int nextBreak = myHeader.indexOf(","); |
|
||||||
if(nextBreak < 0){ |
|
||||||
//os_printf("AUTH FAIL: no variables\n");
|
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
String myUsername = String(); |
|
||||||
String myRealm = String(); |
|
||||||
String myNonce = String(); |
|
||||||
String myUri = String(); |
|
||||||
String myResponse = String(); |
|
||||||
String myQop = String(); |
|
||||||
String myNc = String(); |
|
||||||
String myCnonce = String(); |
|
||||||
|
|
||||||
myHeader += ", "; |
|
||||||
do { |
|
||||||
String avLine = myHeader.substring(0, nextBreak); |
|
||||||
avLine.trim(); |
|
||||||
myHeader = myHeader.substring(nextBreak+1); |
|
||||||
nextBreak = myHeader.indexOf(","); |
|
||||||
|
|
||||||
int eqSign = avLine.indexOf("="); |
|
||||||
if(eqSign < 0){ |
|
||||||
//os_printf("AUTH FAIL: no = sign\n");
|
|
||||||
return false; |
|
||||||
} |
|
||||||
String varName = avLine.substring(0, eqSign); |
|
||||||
avLine = avLine.substring(eqSign + 1); |
|
||||||
if(avLine.startsWith("\"")){ |
|
||||||
avLine = avLine.substring(1, avLine.length() - 1); |
|
||||||
} |
|
||||||
|
|
||||||
if(varName.equals("username")){ |
|
||||||
if(!avLine.equals(username)){ |
|
||||||
//os_printf("AUTH FAIL: username\n");
|
|
||||||
return false; |
|
||||||
} |
|
||||||
myUsername = avLine; |
|
||||||
} else if(varName.equals("realm")){ |
|
||||||
if(realm != NULL && !avLine.equals(realm)){ |
|
||||||
//os_printf("AUTH FAIL: realm\n");
|
|
||||||
return false; |
|
||||||
} |
|
||||||
myRealm = avLine; |
|
||||||
} else if(varName.equals("nonce")){ |
|
||||||
if(nonce != NULL && !avLine.equals(nonce)){ |
|
||||||
//os_printf("AUTH FAIL: nonce\n");
|
|
||||||
return false; |
|
||||||
} |
|
||||||
myNonce = avLine; |
|
||||||
} else if(varName.equals("opaque")){ |
|
||||||
if(opaque != NULL && !avLine.equals(opaque)){ |
|
||||||
//os_printf("AUTH FAIL: opaque\n");
|
|
||||||
return false; |
|
||||||
} |
|
||||||
} else if(varName.equals("uri")){ |
|
||||||
if(uri != NULL && !avLine.equals(uri)){ |
|
||||||
//os_printf("AUTH FAIL: uri\n");
|
|
||||||
return false; |
|
||||||
} |
|
||||||
myUri = avLine; |
|
||||||
} else if(varName.equals("response")){ |
|
||||||
myResponse = avLine; |
|
||||||
} else if(varName.equals("qop")){ |
|
||||||
myQop = avLine; |
|
||||||
} else if(varName.equals("nc")){ |
|
||||||
myNc = avLine; |
|
||||||
} else if(varName.equals("cnonce")){ |
|
||||||
myCnonce = avLine; |
|
||||||
} |
|
||||||
} while(nextBreak > 0); |
|
||||||
|
|
||||||
String ha1 = (passwordIsHash) ? String(password) : stringMD5(myUsername + ":" + myRealm + ":" + String(password)); |
|
||||||
String ha2 = String(method) + ":" + myUri; |
|
||||||
String response = ha1 + ":" + myNonce + ":" + myNc + ":" + myCnonce + ":" + myQop + ":" + stringMD5(ha2); |
|
||||||
|
|
||||||
if(myResponse.equals(stringMD5(response))){ |
|
||||||
//os_printf("AUTH SUCCESS\n");
|
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
//os_printf("AUTH FAIL: password\n");
|
|
||||||
return false; |
|
||||||
} |
|
@ -1,34 +0,0 @@ |
|||||||
/*
|
|
||||||
Asynchronous WebServer library for Espressif MCUs |
|
||||||
|
|
||||||
Copyright (c) 2016 Hristo Gochkov. All rights reserved. |
|
||||||
This file is part of the esp8266 core for Arduino environment. |
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or |
|
||||||
modify it under the terms of the GNU Lesser General Public |
|
||||||
License as published by the Free Software Foundation; either |
|
||||||
version 2.1 of the License, or (at your option) any later version. |
|
||||||
|
|
||||||
This library is distributed in the hope that it will be useful, |
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
||||||
Lesser General Public License for more details. |
|
||||||
|
|
||||||
You should have received a copy of the GNU Lesser General Public |
|
||||||
License along with this library; if not, write to the Free Software |
|
||||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
|
||||||
*/ |
|
||||||
|
|
||||||
#ifndef WEB_AUTHENTICATION_H_ |
|
||||||
#define WEB_AUTHENTICATION_H_ |
|
||||||
|
|
||||||
#include "Arduino.h" |
|
||||||
|
|
||||||
bool checkBasicAuthentication(const char * header, const char * username, const char * password); |
|
||||||
String requestDigestAuthentication(const char * realm); |
|
||||||
bool checkDigestAuthentication(const char * header, const char * method, const char * username, const char * password, const char * realm, bool passwordIsHash, const char * nonce, const char * opaque, const char * uri); |
|
||||||
|
|
||||||
//for storing hashed versions on the device that can be authenticated against
|
|
||||||
String generateDigestHash(const char * username, const char * password, const char * realm); |
|
||||||
|
|
||||||
#endif |
|
@ -1,151 +0,0 @@ |
|||||||
/*
|
|
||||||
Asynchronous WebServer library for Espressif MCUs |
|
||||||
|
|
||||||
Copyright (c) 2016 Hristo Gochkov. All rights reserved. |
|
||||||
This file is part of the esp8266 core for Arduino environment. |
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or |
|
||||||
modify it under the terms of the GNU Lesser General Public |
|
||||||
License as published by the Free Software Foundation; either |
|
||||||
version 2.1 of the License, or (at your option) any later version. |
|
||||||
|
|
||||||
This library is distributed in the hope that it will be useful, |
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
||||||
Lesser General Public License for more details. |
|
||||||
|
|
||||||
You should have received a copy of the GNU Lesser General Public |
|
||||||
License along with this library; if not, write to the Free Software |
|
||||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
|
||||||
*/ |
|
||||||
#ifndef ASYNCWEBSERVERHANDLERIMPL_H_ |
|
||||||
#define ASYNCWEBSERVERHANDLERIMPL_H_ |
|
||||||
|
|
||||||
#include <string> |
|
||||||
#ifdef ASYNCWEBSERVER_REGEX |
|
||||||
#include <regex> |
|
||||||
#endif |
|
||||||
|
|
||||||
#include "stddef.h" |
|
||||||
#include <time.h> |
|
||||||
|
|
||||||
class AsyncStaticWebHandler: public AsyncWebHandler { |
|
||||||
using File = fs::File; |
|
||||||
using FS = fs::FS; |
|
||||||
private: |
|
||||||
bool _getFile(AsyncWebServerRequest *request); |
|
||||||
bool _fileExists(AsyncWebServerRequest *request, const String& path); |
|
||||||
uint8_t _countBits(const uint8_t value) const; |
|
||||||
protected: |
|
||||||
FS _fs; |
|
||||||
String _uri; |
|
||||||
String _path; |
|
||||||
String _default_file; |
|
||||||
String _cache_control; |
|
||||||
String _last_modified; |
|
||||||
AwsTemplateProcessor _callback; |
|
||||||
bool _isDir; |
|
||||||
bool _gzipFirst; |
|
||||||
uint8_t _gzipStats; |
|
||||||
public: |
|
||||||
AsyncStaticWebHandler(const char* uri, FS& fs, const char* path, const char* cache_control); |
|
||||||
virtual bool canHandle(AsyncWebServerRequest *request) override final; |
|
||||||
virtual void handleRequest(AsyncWebServerRequest *request) override final; |
|
||||||
AsyncStaticWebHandler& setIsDir(bool isDir); |
|
||||||
AsyncStaticWebHandler& setDefaultFile(const char* filename); |
|
||||||
AsyncStaticWebHandler& setCacheControl(const char* cache_control); |
|
||||||
AsyncStaticWebHandler& setLastModified(const char* last_modified); |
|
||||||
AsyncStaticWebHandler& setLastModified(struct tm* last_modified); |
|
||||||
#ifdef ESP8266 |
|
||||||
AsyncStaticWebHandler& setLastModified(time_t last_modified); |
|
||||||
AsyncStaticWebHandler& setLastModified(); //sets to current time. Make sure sntp is runing and time is updated
|
|
||||||
#endif |
|
||||||
AsyncStaticWebHandler& setTemplateProcessor(AwsTemplateProcessor newCallback) {_callback = newCallback; return *this;} |
|
||||||
}; |
|
||||||
|
|
||||||
class AsyncCallbackWebHandler: public AsyncWebHandler { |
|
||||||
private: |
|
||||||
protected: |
|
||||||
String _uri; |
|
||||||
WebRequestMethodComposite _method; |
|
||||||
ArRequestHandlerFunction _onRequest; |
|
||||||
ArUploadHandlerFunction _onUpload; |
|
||||||
ArBodyHandlerFunction _onBody; |
|
||||||
bool _isRegex; |
|
||||||
public: |
|
||||||
AsyncCallbackWebHandler() : _uri(), _method(HTTP_ANY), _onRequest(NULL), _onUpload(NULL), _onBody(NULL), _isRegex(false) {} |
|
||||||
void setUri(const String& uri){
|
|
||||||
_uri = uri;
|
|
||||||
_isRegex = uri.startsWith("^") && uri.endsWith("$"); |
|
||||||
} |
|
||||||
void setMethod(WebRequestMethodComposite method){ _method = method; } |
|
||||||
void onRequest(ArRequestHandlerFunction fn){ _onRequest = fn; } |
|
||||||
void onUpload(ArUploadHandlerFunction fn){ _onUpload = fn; } |
|
||||||
void onBody(ArBodyHandlerFunction fn){ _onBody = fn; } |
|
||||||
|
|
||||||
virtual bool canHandle(AsyncWebServerRequest *request) override final{ |
|
||||||
|
|
||||||
if(!_onRequest) |
|
||||||
return false; |
|
||||||
|
|
||||||
if(!(_method & request->method())) |
|
||||||
return false; |
|
||||||
|
|
||||||
#ifdef ASYNCWEBSERVER_REGEX |
|
||||||
if (_isRegex) { |
|
||||||
std::regex pattern(_uri.c_str()); |
|
||||||
std::smatch matches; |
|
||||||
std::string s(request->url().c_str()); |
|
||||||
if(std::regex_search(s, matches, pattern)) { |
|
||||||
for (size_t i = 1; i < matches.size(); ++i) { // start from 1
|
|
||||||
request->_addPathParam(matches[i].str().c_str()); |
|
||||||
} |
|
||||||
} else { |
|
||||||
return false; |
|
||||||
} |
|
||||||
} else
|
|
||||||
#endif |
|
||||||
if (_uri.length() && _uri.startsWith("/*.")) { |
|
||||||
String uriTemplate = String (_uri); |
|
||||||
uriTemplate = uriTemplate.substring(uriTemplate.lastIndexOf(".")); |
|
||||||
if (!request->url().endsWith(uriTemplate)) |
|
||||||
return false; |
|
||||||
} |
|
||||||
else |
|
||||||
if (_uri.length() && _uri.endsWith("*")) { |
|
||||||
String uriTemplate = String(_uri); |
|
||||||
uriTemplate = uriTemplate.substring(0, uriTemplate.length() - 1); |
|
||||||
if (!request->url().startsWith(uriTemplate)) |
|
||||||
return false; |
|
||||||
} |
|
||||||
else if(_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri+"/"))) |
|
||||||
return false; |
|
||||||
|
|
||||||
request->addInterestingHeader("ANY"); |
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
virtual void handleRequest(AsyncWebServerRequest *request) override final { |
|
||||||
if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str())) |
|
||||||
return request->requestAuthentication(); |
|
||||||
if(_onRequest) |
|
||||||
_onRequest(request); |
|
||||||
else |
|
||||||
request->send(500); |
|
||||||
} |
|
||||||
virtual void handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) override final { |
|
||||||
if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str())) |
|
||||||
return request->requestAuthentication(); |
|
||||||
if(_onUpload) |
|
||||||
_onUpload(request, filename, index, data, len, final); |
|
||||||
} |
|
||||||
virtual void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override final { |
|
||||||
if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str())) |
|
||||||
return request->requestAuthentication(); |
|
||||||
if(_onBody) |
|
||||||
_onBody(request, data, len, index, total); |
|
||||||
} |
|
||||||
virtual bool isRequestHandlerTrivial() override final {return _onRequest ? false : true;} |
|
||||||
}; |
|
||||||
|
|
||||||
#endif /* ASYNCWEBSERVERHANDLERIMPL_H_ */ |
|
@ -1,220 +0,0 @@ |
|||||||
/*
|
|
||||||
Asynchronous WebServer library for Espressif MCUs |
|
||||||
|
|
||||||
Copyright (c) 2016 Hristo Gochkov. All rights reserved. |
|
||||||
This file is part of the esp8266 core for Arduino environment. |
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or |
|
||||||
modify it under the terms of the GNU Lesser General Public |
|
||||||
License as published by the Free Software Foundation; either |
|
||||||
version 2.1 of the License, or (at your option) any later version. |
|
||||||
|
|
||||||
This library is distributed in the hope that it will be useful, |
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
||||||
Lesser General Public License for more details. |
|
||||||
|
|
||||||
You should have received a copy of the GNU Lesser General Public |
|
||||||
License along with this library; if not, write to the Free Software |
|
||||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
|
||||||
*/ |
|
||||||
#include "ESPAsyncWebServer.h" |
|
||||||
#include "WebHandlerImpl.h" |
|
||||||
|
|
||||||
AsyncStaticWebHandler::AsyncStaticWebHandler(const char* uri, FS& fs, const char* path, const char* cache_control) |
|
||||||
: _fs(fs), _uri(uri), _path(path), _default_file("index.htm"), _cache_control(cache_control), _last_modified(""), _callback(nullptr) |
|
||||||
{ |
|
||||||
// Ensure leading '/'
|
|
||||||
if (_uri.length() == 0 || _uri[0] != '/') _uri = "/" + _uri; |
|
||||||
if (_path.length() == 0 || _path[0] != '/') _path = "/" + _path; |
|
||||||
|
|
||||||
// If path ends with '/' we assume a hint that this is a directory to improve performance.
|
|
||||||
// However - if it does not end with '/' we, can't assume a file, path can still be a directory.
|
|
||||||
_isDir = _path[_path.length()-1] == '/'; |
|
||||||
|
|
||||||
// Remove the trailing '/' so we can handle default file
|
|
||||||
// Notice that root will be "" not "/"
|
|
||||||
if (_uri[_uri.length()-1] == '/') _uri = _uri.substring(0, _uri.length()-1); |
|
||||||
if (_path[_path.length()-1] == '/') _path = _path.substring(0, _path.length()-1); |
|
||||||
|
|
||||||
// Reset stats
|
|
||||||
_gzipFirst = false; |
|
||||||
_gzipStats = 0xF8; |
|
||||||
} |
|
||||||
|
|
||||||
AsyncStaticWebHandler& AsyncStaticWebHandler::setIsDir(bool isDir){ |
|
||||||
_isDir = isDir; |
|
||||||
return *this; |
|
||||||
} |
|
||||||
|
|
||||||
AsyncStaticWebHandler& AsyncStaticWebHandler::setDefaultFile(const char* filename){ |
|
||||||
_default_file = String(filename); |
|
||||||
return *this; |
|
||||||
} |
|
||||||
|
|
||||||
AsyncStaticWebHandler& AsyncStaticWebHandler::setCacheControl(const char* cache_control){ |
|
||||||
_cache_control = String(cache_control); |
|
||||||
return *this; |
|
||||||
} |
|
||||||
|
|
||||||
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(const char* last_modified){ |
|
||||||
_last_modified = String(last_modified); |
|
||||||
return *this; |
|
||||||
} |
|
||||||
|
|
||||||
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(struct tm* last_modified){ |
|
||||||
char result[30]; |
|
||||||
strftime (result,30,"%a, %d %b %Y %H:%M:%S %Z", last_modified); |
|
||||||
return setLastModified((const char *)result); |
|
||||||
} |
|
||||||
|
|
||||||
#ifdef ESP8266 |
|
||||||
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(time_t last_modified){ |
|
||||||
return setLastModified((struct tm *)gmtime(&last_modified)); |
|
||||||
} |
|
||||||
|
|
||||||
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(){ |
|
||||||
time_t last_modified; |
|
||||||
if(time(&last_modified) == 0) //time is not yet set
|
|
||||||
return *this; |
|
||||||
return setLastModified(last_modified); |
|
||||||
} |
|
||||||
#endif |
|
||||||
bool AsyncStaticWebHandler::canHandle(AsyncWebServerRequest *request){ |
|
||||||
if(request->method() != HTTP_GET
|
|
||||||
|| !request->url().startsWith(_uri)
|
|
||||||
|| !request->isExpectedRequestedConnType(RCT_DEFAULT, RCT_HTTP) |
|
||||||
){ |
|
||||||
return false; |
|
||||||
} |
|
||||||
if (_getFile(request)) { |
|
||||||
// We interested in "If-Modified-Since" header to check if file was modified
|
|
||||||
if (_last_modified.length()) |
|
||||||
request->addInterestingHeader("If-Modified-Since"); |
|
||||||
|
|
||||||
if(_cache_control.length()) |
|
||||||
request->addInterestingHeader("If-None-Match"); |
|
||||||
|
|
||||||
DEBUGF("[AsyncStaticWebHandler::canHandle] TRUE\n"); |
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
bool AsyncStaticWebHandler::_getFile(AsyncWebServerRequest *request) |
|
||||||
{ |
|
||||||
// Remove the found uri
|
|
||||||
String path = request->url().substring(_uri.length()); |
|
||||||
|
|
||||||
// We can skip the file check and look for default if request is to the root of a directory or that request path ends with '/'
|
|
||||||
bool canSkipFileCheck = (_isDir && path.length() == 0) || (path.length() && path[path.length()-1] == '/'); |
|
||||||
|
|
||||||
path = _path + path; |
|
||||||
|
|
||||||
// Do we have a file or .gz file
|
|
||||||
if (!canSkipFileCheck && _fileExists(request, path)) |
|
||||||
return true; |
|
||||||
|
|
||||||
// Can't handle if not default file
|
|
||||||
if (_default_file.length() == 0) |
|
||||||
return false; |
|
||||||
|
|
||||||
// Try to add default file, ensure there is a trailing '/' ot the path.
|
|
||||||
if (path.length() == 0 || path[path.length()-1] != '/') |
|
||||||
path += "/"; |
|
||||||
path += _default_file; |
|
||||||
|
|
||||||
return _fileExists(request, path); |
|
||||||
} |
|
||||||
|
|
||||||
#ifdef ESP32 |
|
||||||
#define FILE_IS_REAL(f) (f == true && !f.isDirectory()) |
|
||||||
#else |
|
||||||
#define FILE_IS_REAL(f) (f == true) |
|
||||||
#endif |
|
||||||
|
|
||||||
bool AsyncStaticWebHandler::_fileExists(AsyncWebServerRequest *request, const String& path) |
|
||||||
{ |
|
||||||
bool fileFound = false; |
|
||||||
bool gzipFound = false; |
|
||||||
|
|
||||||
String gzip = path + ".gz"; |
|
||||||
|
|
||||||
if (_gzipFirst) { |
|
||||||
request->_tempFile = _fs.open(gzip, "r"); |
|
||||||
gzipFound = FILE_IS_REAL(request->_tempFile); |
|
||||||
if (!gzipFound){ |
|
||||||
request->_tempFile = _fs.open(path, "r"); |
|
||||||
fileFound = FILE_IS_REAL(request->_tempFile); |
|
||||||
} |
|
||||||
} else { |
|
||||||
request->_tempFile = _fs.open(path, "r"); |
|
||||||
fileFound = FILE_IS_REAL(request->_tempFile); |
|
||||||
if (!fileFound){ |
|
||||||
request->_tempFile = _fs.open(gzip, "r"); |
|
||||||
gzipFound = FILE_IS_REAL(request->_tempFile); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
bool found = fileFound || gzipFound; |
|
||||||
|
|
||||||
if (found) { |
|
||||||
// Extract the file name from the path and keep it in _tempObject
|
|
||||||
size_t pathLen = path.length(); |
|
||||||
char * _tempPath = (char*)malloc(pathLen+1); |
|
||||||
snprintf(_tempPath, pathLen+1, "%s", path.c_str()); |
|
||||||
request->_tempObject = (void*)_tempPath; |
|
||||||
|
|
||||||
// Calculate gzip statistic
|
|
||||||
_gzipStats = (_gzipStats << 1) + (gzipFound ? 1 : 0); |
|
||||||
if (_gzipStats == 0x00) _gzipFirst = false; // All files are not gzip
|
|
||||||
else if (_gzipStats == 0xFF) _gzipFirst = true; // All files are gzip
|
|
||||||
else _gzipFirst = _countBits(_gzipStats) > 4; // IF we have more gzip files - try gzip first
|
|
||||||
} |
|
||||||
|
|
||||||
return found; |
|
||||||
} |
|
||||||
|
|
||||||
uint8_t AsyncStaticWebHandler::_countBits(const uint8_t value) const |
|
||||||
{ |
|
||||||
uint8_t w = value; |
|
||||||
uint8_t n; |
|
||||||
for (n=0; w!=0; n++) w&=w-1; |
|
||||||
return n; |
|
||||||
} |
|
||||||
|
|
||||||
void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest *request) |
|
||||||
{ |
|
||||||
// Get the filename from request->_tempObject and free it
|
|
||||||
String filename = String((char*)request->_tempObject); |
|
||||||
free(request->_tempObject); |
|
||||||
request->_tempObject = NULL; |
|
||||||
if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str())) |
|
||||||
return request->requestAuthentication(); |
|
||||||
|
|
||||||
if (request->_tempFile == true) { |
|
||||||
String etag = String(request->_tempFile.size()); |
|
||||||
if (_last_modified.length() && _last_modified == request->header("If-Modified-Since")) { |
|
||||||
request->_tempFile.close(); |
|
||||||
request->send(304); // Not modified
|
|
||||||
} else if (_cache_control.length() && request->hasHeader("If-None-Match") && request->header("If-None-Match").equals(etag)) { |
|
||||||
request->_tempFile.close(); |
|
||||||
AsyncWebServerResponse * response = new AsyncBasicResponse(304); // Not modified
|
|
||||||
response->addHeader("Cache-Control", _cache_control); |
|
||||||
response->addHeader("ETag", etag); |
|
||||||
request->send(response); |
|
||||||
} else { |
|
||||||
AsyncWebServerResponse * response = new AsyncFileResponse(request->_tempFile, filename, String(), false, _callback); |
|
||||||
if (_last_modified.length()) |
|
||||||
response->addHeader("Last-Modified", _last_modified); |
|
||||||
if (_cache_control.length()){ |
|
||||||
response->addHeader("Cache-Control", _cache_control); |
|
||||||
response->addHeader("ETag", etag); |
|
||||||
} |
|
||||||
request->send(response); |
|
||||||
} |
|
||||||
} else { |
|
||||||
request->send(404); |
|
||||||
} |
|
||||||
} |
|
File diff suppressed because it is too large
Load Diff
@ -1,136 +0,0 @@ |
|||||||
/*
|
|
||||||
Asynchronous WebServer library for Espressif MCUs |
|
||||||
|
|
||||||
Copyright (c) 2016 Hristo Gochkov. All rights reserved. |
|
||||||
This file is part of the esp8266 core for Arduino environment. |
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or |
|
||||||
modify it under the terms of the GNU Lesser General Public |
|
||||||
License as published by the Free Software Foundation; either |
|
||||||
version 2.1 of the License, or (at your option) any later version. |
|
||||||
|
|
||||||
This library is distributed in the hope that it will be useful, |
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
||||||
Lesser General Public License for more details. |
|
||||||
|
|
||||||
You should have received a copy of the GNU Lesser General Public |
|
||||||
License along with this library; if not, write to the Free Software |
|
||||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
|
||||||
*/ |
|
||||||
#ifndef ASYNCWEBSERVERRESPONSEIMPL_H_ |
|
||||||
#define ASYNCWEBSERVERRESPONSEIMPL_H_ |
|
||||||
|
|
||||||
#ifdef Arduino_h |
|
||||||
// arduino is not compatible with std::vector
|
|
||||||
#undef min |
|
||||||
#undef max |
|
||||||
#endif |
|
||||||
#include <vector> |
|
||||||
// It is possible to restore these defines, but one can use _min and _max instead. Or std::min, std::max.
|
|
||||||
|
|
||||||
class AsyncBasicResponse: public AsyncWebServerResponse { |
|
||||||
private: |
|
||||||
String _content; |
|
||||||
public: |
|
||||||
AsyncBasicResponse(int code, const String& contentType=String(), const String& content=String()); |
|
||||||
void _respond(AsyncWebServerRequest *request); |
|
||||||
size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); |
|
||||||
bool _sourceValid() const { return true; } |
|
||||||
}; |
|
||||||
|
|
||||||
class AsyncAbstractResponse: public AsyncWebServerResponse { |
|
||||||
private: |
|
||||||
String _head; |
|
||||||
// Data is inserted into cache at begin().
|
|
||||||
// This is inefficient with vector, but if we use some other container,
|
|
||||||
// we won't be able to access it as contiguous array of bytes when reading from it,
|
|
||||||
// so by gaining performance in one place, we'll lose it in another.
|
|
||||||
std::vector<uint8_t> _cache; |
|
||||||
size_t _readDataFromCacheOrContent(uint8_t* data, const size_t len); |
|
||||||
size_t _fillBufferAndProcessTemplates(uint8_t* buf, size_t maxLen); |
|
||||||
protected: |
|
||||||
AwsTemplateProcessor _callback; |
|
||||||
public: |
|
||||||
AsyncAbstractResponse(AwsTemplateProcessor callback=nullptr); |
|
||||||
void _respond(AsyncWebServerRequest *request); |
|
||||||
size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); |
|
||||||
bool _sourceValid() const { return false; } |
|
||||||
virtual size_t _fillBuffer(uint8_t *buf __attribute__((unused)), size_t maxLen __attribute__((unused))) { return 0; } |
|
||||||
}; |
|
||||||
|
|
||||||
#ifndef TEMPLATE_PLACEHOLDER |
|
||||||
#define TEMPLATE_PLACEHOLDER '%' |
|
||||||
#endif |
|
||||||
|
|
||||||
#define TEMPLATE_PARAM_NAME_LENGTH 32 |
|
||||||
class AsyncFileResponse: public AsyncAbstractResponse { |
|
||||||
using File = fs::File; |
|
||||||
using FS = fs::FS; |
|
||||||
private: |
|
||||||
File _content; |
|
||||||
String _path; |
|
||||||
void _setContentType(const String& path); |
|
||||||
public: |
|
||||||
AsyncFileResponse(FS &fs, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr); |
|
||||||
AsyncFileResponse(File content, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr); |
|
||||||
~AsyncFileResponse(); |
|
||||||
bool _sourceValid() const { return !!(_content); } |
|
||||||
virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; |
|
||||||
}; |
|
||||||
|
|
||||||
class AsyncStreamResponse: public AsyncAbstractResponse { |
|
||||||
private: |
|
||||||
Stream *_content; |
|
||||||
public: |
|
||||||
AsyncStreamResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback=nullptr); |
|
||||||
bool _sourceValid() const { return !!(_content); } |
|
||||||
virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; |
|
||||||
}; |
|
||||||
|
|
||||||
class AsyncCallbackResponse: public AsyncAbstractResponse { |
|
||||||
private: |
|
||||||
AwsResponseFiller _content; |
|
||||||
size_t _filledLength; |
|
||||||
public: |
|
||||||
AsyncCallbackResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr); |
|
||||||
bool _sourceValid() const { return !!(_content); } |
|
||||||
virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; |
|
||||||
}; |
|
||||||
|
|
||||||
class AsyncChunkedResponse: public AsyncAbstractResponse { |
|
||||||
private: |
|
||||||
AwsResponseFiller _content; |
|
||||||
size_t _filledLength; |
|
||||||
public: |
|
||||||
AsyncChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr); |
|
||||||
bool _sourceValid() const { return !!(_content); } |
|
||||||
virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; |
|
||||||
}; |
|
||||||
|
|
||||||
class AsyncProgmemResponse: public AsyncAbstractResponse { |
|
||||||
private: |
|
||||||
const uint8_t * _content; |
|
||||||
size_t _readLength; |
|
||||||
public: |
|
||||||
AsyncProgmemResponse(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback=nullptr); |
|
||||||
bool _sourceValid() const { return true; } |
|
||||||
virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; |
|
||||||
}; |
|
||||||
|
|
||||||
class cbuf; |
|
||||||
|
|
||||||
class AsyncResponseStream: public AsyncAbstractResponse, public Print { |
|
||||||
private: |
|
||||||
cbuf *_content; |
|
||||||
public: |
|
||||||
AsyncResponseStream(const String& contentType, size_t bufferSize); |
|
||||||
~AsyncResponseStream(); |
|
||||||
bool _sourceValid() const { return (_state < RESPONSE_END); } |
|
||||||
virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; |
|
||||||
size_t write(const uint8_t *data, size_t len); |
|
||||||
size_t write(uint8_t data); |
|
||||||
using Print::write; |
|
||||||
}; |
|
||||||
|
|
||||||
#endif /* ASYNCWEBSERVERRESPONSEIMPL_H_ */ |
|
@ -1,699 +0,0 @@ |
|||||||
/*
|
|
||||||
Asynchronous WebServer library for Espressif MCUs |
|
||||||
|
|
||||||
Copyright (c) 2016 Hristo Gochkov. All rights reserved. |
|
||||||
This file is part of the esp8266 core for Arduino environment. |
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or |
|
||||||
modify it under the terms of the GNU Lesser General Public |
|
||||||
License as published by the Free Software Foundation; either |
|
||||||
version 2.1 of the License, or (at your option) any later version. |
|
||||||
|
|
||||||
This library is distributed in the hope that it will be useful, |
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
||||||
Lesser General Public License for more details. |
|
||||||
|
|
||||||
You should have received a copy of the GNU Lesser General Public |
|
||||||
License along with this library; if not, write to the Free Software |
|
||||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
|
||||||
*/ |
|
||||||
#include "ESPAsyncWebServer.h" |
|
||||||
#include "WebResponseImpl.h" |
|
||||||
#include "cbuf.h" |
|
||||||
|
|
||||||
// Since ESP8266 does not link memchr by default, here's its implementation.
|
|
||||||
void* memchr(void* ptr, int ch, size_t count) |
|
||||||
{ |
|
||||||
unsigned char* p = static_cast<unsigned char*>(ptr); |
|
||||||
while(count--) |
|
||||||
if(*p++ == static_cast<unsigned char>(ch)) |
|
||||||
return --p; |
|
||||||
return nullptr; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Abstract Response |
|
||||||
* */ |
|
||||||
const char* AsyncWebServerResponse::_responseCodeToString(int code) { |
|
||||||
switch (code) { |
|
||||||
case 100: return "Continue"; |
|
||||||
case 101: return "Switching Protocols"; |
|
||||||
case 200: return "OK"; |
|
||||||
case 201: return "Created"; |
|
||||||
case 202: return "Accepted"; |
|
||||||
case 203: return "Non-Authoritative Information"; |
|
||||||
case 204: return "No Content"; |
|
||||||
case 205: return "Reset Content"; |
|
||||||
case 206: return "Partial Content"; |
|
||||||
case 300: return "Multiple Choices"; |
|
||||||
case 301: return "Moved Permanently"; |
|
||||||
case 302: return "Found"; |
|
||||||
case 303: return "See Other"; |
|
||||||
case 304: return "Not Modified"; |
|
||||||
case 305: return "Use Proxy"; |
|
||||||
case 307: return "Temporary Redirect"; |
|
||||||
case 400: return "Bad Request"; |
|
||||||
case 401: return "Unauthorized"; |
|
||||||
case 402: return "Payment Required"; |
|
||||||
case 403: return "Forbidden"; |
|
||||||
case 404: return "Not Found"; |
|
||||||
case 405: return "Method Not Allowed"; |
|
||||||
case 406: return "Not Acceptable"; |
|
||||||
case 407: return "Proxy Authentication Required"; |
|
||||||
case 408: return "Request Time-out"; |
|
||||||
case 409: return "Conflict"; |
|
||||||
case 410: return "Gone"; |
|
||||||
case 411: return "Length Required"; |
|
||||||
case 412: return "Precondition Failed"; |
|
||||||
case 413: return "Request Entity Too Large"; |
|
||||||
case 414: return "Request-URI Too Large"; |
|
||||||
case 415: return "Unsupported Media Type"; |
|
||||||
case 416: return "Requested range not satisfiable"; |
|
||||||
case 417: return "Expectation Failed"; |
|
||||||
case 500: return "Internal Server Error"; |
|
||||||
case 501: return "Not Implemented"; |
|
||||||
case 502: return "Bad Gateway"; |
|
||||||
case 503: return "Service Unavailable"; |
|
||||||
case 504: return "Gateway Time-out"; |
|
||||||
case 505: return "HTTP Version not supported"; |
|
||||||
default: return ""; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
AsyncWebServerResponse::AsyncWebServerResponse() |
|
||||||
: _code(0) |
|
||||||
, _headers(LinkedList<AsyncWebHeader *>([](AsyncWebHeader *h){ delete h; })) |
|
||||||
, _contentType() |
|
||||||
, _contentLength(0) |
|
||||||
, _sendContentLength(true) |
|
||||||
, _chunked(false) |
|
||||||
, _headLength(0) |
|
||||||
, _sentLength(0) |
|
||||||
, _ackedLength(0) |
|
||||||
, _writtenLength(0) |
|
||||||
, _state(RESPONSE_SETUP) |
|
||||||
{ |
|
||||||
for(auto header: DefaultHeaders::Instance()) { |
|
||||||
_headers.add(new AsyncWebHeader(header->name(), header->value())); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
AsyncWebServerResponse::~AsyncWebServerResponse(){ |
|
||||||
_headers.free(); |
|
||||||
} |
|
||||||
|
|
||||||
void AsyncWebServerResponse::setCode(int code){ |
|
||||||
if(_state == RESPONSE_SETUP) |
|
||||||
_code = code; |
|
||||||
} |
|
||||||
|
|
||||||
void AsyncWebServerResponse::setContentLength(size_t len){ |
|
||||||
if(_state == RESPONSE_SETUP) |
|
||||||
_contentLength = len; |
|
||||||
} |
|
||||||
|
|
||||||
void AsyncWebServerResponse::setContentType(const String& type){ |
|
||||||
if(_state == RESPONSE_SETUP) |
|
||||||
_contentType = type; |
|
||||||
} |
|
||||||
|
|
||||||
void AsyncWebServerResponse::addHeader(const String& name, const String& value){ |
|
||||||
_headers.add(new AsyncWebHeader(name, value)); |
|
||||||
} |
|
||||||
|
|
||||||
String AsyncWebServerResponse::_assembleHead(uint8_t version){ |
|
||||||
if(version){ |
|
||||||
addHeader("Accept-Ranges","none"); |
|
||||||
if(_chunked) |
|
||||||
addHeader("Transfer-Encoding","chunked"); |
|
||||||
} |
|
||||||
String out = String(); |
|
||||||
int bufSize = 300; |
|
||||||
char buf[bufSize]; |
|
||||||
|
|
||||||
snprintf(buf, bufSize, "HTTP/1.%d %d %s\r\n", version, _code, _responseCodeToString(_code)); |
|
||||||
out.concat(buf); |
|
||||||
|
|
||||||
if(_sendContentLength) { |
|
||||||
snprintf(buf, bufSize, "Content-Length: %d\r\n", _contentLength); |
|
||||||
out.concat(buf); |
|
||||||
} |
|
||||||
if(_contentType.length()) { |
|
||||||
snprintf(buf, bufSize, "Content-Type: %s\r\n", _contentType.c_str()); |
|
||||||
out.concat(buf); |
|
||||||
} |
|
||||||
|
|
||||||
for(const auto& header: _headers){ |
|
||||||
snprintf(buf, bufSize, "%s: %s\r\n", header->name().c_str(), header->value().c_str()); |
|
||||||
out.concat(buf); |
|
||||||
} |
|
||||||
_headers.free(); |
|
||||||
|
|
||||||
out.concat("\r\n"); |
|
||||||
_headLength = out.length(); |
|
||||||
return out; |
|
||||||
} |
|
||||||
|
|
||||||
bool AsyncWebServerResponse::_started() const { return _state > RESPONSE_SETUP; } |
|
||||||
bool AsyncWebServerResponse::_finished() const { return _state > RESPONSE_WAIT_ACK; } |
|
||||||
bool AsyncWebServerResponse::_failed() const { return _state == RESPONSE_FAILED; } |
|
||||||
bool AsyncWebServerResponse::_sourceValid() const { return false; } |
|
||||||
void AsyncWebServerResponse::_respond(AsyncWebServerRequest *request){ _state = RESPONSE_END; request->client()->close(); } |
|
||||||
size_t AsyncWebServerResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){ (void)request; (void)len; (void)time; return 0; } |
|
||||||
|
|
||||||
/*
|
|
||||||
* String/Code Response |
|
||||||
* */ |
|
||||||
AsyncBasicResponse::AsyncBasicResponse(int code, const String& contentType, const String& content){ |
|
||||||
_code = code; |
|
||||||
_content = content; |
|
||||||
_contentType = contentType; |
|
||||||
if(_content.length()){ |
|
||||||
_contentLength = _content.length(); |
|
||||||
if(!_contentType.length()) |
|
||||||
_contentType = "text/plain"; |
|
||||||
} |
|
||||||
addHeader("Connection","close"); |
|
||||||
} |
|
||||||
|
|
||||||
void AsyncBasicResponse::_respond(AsyncWebServerRequest *request){ |
|
||||||
_state = RESPONSE_HEADERS; |
|
||||||
String out = _assembleHead(request->version()); |
|
||||||
size_t outLen = out.length(); |
|
||||||
size_t space = request->client()->space(); |
|
||||||
if(!_contentLength && space >= outLen){ |
|
||||||
_writtenLength += request->client()->write(out.c_str(), outLen); |
|
||||||
_state = RESPONSE_WAIT_ACK; |
|
||||||
} else if(_contentLength && space >= outLen + _contentLength){ |
|
||||||
out += _content; |
|
||||||
outLen += _contentLength; |
|
||||||
_writtenLength += request->client()->write(out.c_str(), outLen); |
|
||||||
_state = RESPONSE_WAIT_ACK; |
|
||||||
} else if(space && space < outLen){ |
|
||||||
String partial = out.substring(0, space); |
|
||||||
_content = out.substring(space) + _content; |
|
||||||
_contentLength += outLen - space; |
|
||||||
_writtenLength += request->client()->write(partial.c_str(), partial.length()); |
|
||||||
_state = RESPONSE_CONTENT; |
|
||||||
} else if(space > outLen && space < (outLen + _contentLength)){ |
|
||||||
size_t shift = space - outLen; |
|
||||||
outLen += shift; |
|
||||||
_sentLength += shift; |
|
||||||
out += _content.substring(0, shift); |
|
||||||
_content = _content.substring(shift); |
|
||||||
_writtenLength += request->client()->write(out.c_str(), outLen); |
|
||||||
_state = RESPONSE_CONTENT; |
|
||||||
} else { |
|
||||||
_content = out + _content; |
|
||||||
_contentLength += outLen; |
|
||||||
_state = RESPONSE_CONTENT; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
size_t AsyncBasicResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){ |
|
||||||
(void)time; |
|
||||||
_ackedLength += len; |
|
||||||
if(_state == RESPONSE_CONTENT){ |
|
||||||
size_t available = _contentLength - _sentLength; |
|
||||||
size_t space = request->client()->space(); |
|
||||||
//we can fit in this packet
|
|
||||||
if(space > available){ |
|
||||||
_writtenLength += request->client()->write(_content.c_str(), available); |
|
||||||
_content = String(); |
|
||||||
_state = RESPONSE_WAIT_ACK; |
|
||||||
return available; |
|
||||||
} |
|
||||||
//send some data, the rest on ack
|
|
||||||
String out = _content.substring(0, space); |
|
||||||
_content = _content.substring(space); |
|
||||||
_sentLength += space; |
|
||||||
_writtenLength += request->client()->write(out.c_str(), space); |
|
||||||
return space; |
|
||||||
} else if(_state == RESPONSE_WAIT_ACK){ |
|
||||||
if(_ackedLength >= _writtenLength){ |
|
||||||
_state = RESPONSE_END; |
|
||||||
} |
|
||||||
} |
|
||||||
return 0; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Abstract Response |
|
||||||
* */ |
|
||||||
|
|
||||||
AsyncAbstractResponse::AsyncAbstractResponse(AwsTemplateProcessor callback): _callback(callback) |
|
||||||
{ |
|
||||||
// In case of template processing, we're unable to determine real response size
|
|
||||||
if(callback) { |
|
||||||
_contentLength = 0; |
|
||||||
_sendContentLength = false; |
|
||||||
_chunked = true; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
void AsyncAbstractResponse::_respond(AsyncWebServerRequest *request){ |
|
||||||
addHeader("Connection","close"); |
|
||||||
_head = _assembleHead(request->version()); |
|
||||||
_state = RESPONSE_HEADERS; |
|
||||||
_ack(request, 0, 0); |
|
||||||
} |
|
||||||
|
|
||||||
size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){ |
|
||||||
(void)time; |
|
||||||
if(!_sourceValid()){ |
|
||||||
_state = RESPONSE_FAILED; |
|
||||||
request->client()->close(); |
|
||||||
return 0; |
|
||||||
} |
|
||||||
_ackedLength += len; |
|
||||||
size_t space = request->client()->space(); |
|
||||||
|
|
||||||
size_t headLen = _head.length(); |
|
||||||
if(_state == RESPONSE_HEADERS){ |
|
||||||
if(space >= headLen){ |
|
||||||
_state = RESPONSE_CONTENT; |
|
||||||
space -= headLen; |
|
||||||
} else { |
|
||||||
String out = _head.substring(0, space); |
|
||||||
_head = _head.substring(space); |
|
||||||
_writtenLength += request->client()->write(out.c_str(), out.length()); |
|
||||||
return out.length(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
if(_state == RESPONSE_CONTENT){ |
|
||||||
size_t outLen; |
|
||||||
if(_chunked){ |
|
||||||
if(space <= 8){ |
|
||||||
return 0; |
|
||||||
} |
|
||||||
outLen = space; |
|
||||||
} else if(!_sendContentLength){ |
|
||||||
outLen = space; |
|
||||||
} else { |
|
||||||
outLen = ((_contentLength - _sentLength) > space)?space:(_contentLength - _sentLength); |
|
||||||
} |
|
||||||
|
|
||||||
uint8_t *buf = (uint8_t *)malloc(outLen+headLen); |
|
||||||
if (!buf) { |
|
||||||
// os_printf("_ack malloc %d failed\n", outLen+headLen);
|
|
||||||
return 0; |
|
||||||
} |
|
||||||
|
|
||||||
if(headLen){ |
|
||||||
memcpy(buf, _head.c_str(), _head.length()); |
|
||||||
} |
|
||||||
|
|
||||||
size_t readLen = 0; |
|
||||||
|
|
||||||
if(_chunked){ |
|
||||||
// HTTP 1.1 allows leading zeros in chunk length. Or spaces may be added.
|
|
||||||
// See RFC2616 sections 2, 3.6.1.
|
|
||||||
readLen = _fillBufferAndProcessTemplates(buf+headLen+6, outLen - 8); |
|
||||||
if(readLen == RESPONSE_TRY_AGAIN){ |
|
||||||
free(buf); |
|
||||||
return 0; |
|
||||||
} |
|
||||||
outLen = sprintf((char*)buf+headLen, "%x", readLen) + headLen; |
|
||||||
while(outLen < headLen + 4) buf[outLen++] = ' '; |
|
||||||
buf[outLen++] = '\r'; |
|
||||||
buf[outLen++] = '\n'; |
|
||||||
outLen += readLen; |
|
||||||
buf[outLen++] = '\r'; |
|
||||||
buf[outLen++] = '\n'; |
|
||||||
} else { |
|
||||||
readLen = _fillBufferAndProcessTemplates(buf+headLen, outLen); |
|
||||||
if(readLen == RESPONSE_TRY_AGAIN){ |
|
||||||
free(buf); |
|
||||||
return 0; |
|
||||||
} |
|
||||||
outLen = readLen + headLen; |
|
||||||
} |
|
||||||
|
|
||||||
if(headLen){ |
|
||||||
_head = String(); |
|
||||||
} |
|
||||||
|
|
||||||
if(outLen){ |
|
||||||
_writtenLength += request->client()->write((const char*)buf, outLen); |
|
||||||
} |
|
||||||
|
|
||||||
if(_chunked){ |
|
||||||
_sentLength += readLen; |
|
||||||
} else { |
|
||||||
_sentLength += outLen - headLen; |
|
||||||
} |
|
||||||
|
|
||||||
free(buf); |
|
||||||
|
|
||||||
if((_chunked && readLen == 0) || (!_sendContentLength && outLen == 0) || (!_chunked && _sentLength == _contentLength)){ |
|
||||||
_state = RESPONSE_WAIT_ACK; |
|
||||||
} |
|
||||||
return outLen; |
|
||||||
|
|
||||||
} else if(_state == RESPONSE_WAIT_ACK){ |
|
||||||
if(!_sendContentLength || _ackedLength >= _writtenLength){ |
|
||||||
_state = RESPONSE_END; |
|
||||||
if(!_chunked && !_sendContentLength) |
|
||||||
request->client()->close(true); |
|
||||||
} |
|
||||||
} |
|
||||||
return 0; |
|
||||||
} |
|
||||||
|
|
||||||
size_t AsyncAbstractResponse::_readDataFromCacheOrContent(uint8_t* data, const size_t len) |
|
||||||
{ |
|
||||||
// If we have something in cache, copy it to buffer
|
|
||||||
const size_t readFromCache = std::min(len, _cache.size()); |
|
||||||
if(readFromCache) { |
|
||||||
memcpy(data, _cache.data(), readFromCache); |
|
||||||
_cache.erase(_cache.begin(), _cache.begin() + readFromCache); |
|
||||||
} |
|
||||||
// If we need to read more...
|
|
||||||
const size_t needFromFile = len - readFromCache; |
|
||||||
const size_t readFromContent = _fillBuffer(data + readFromCache, needFromFile); |
|
||||||
return readFromCache + readFromContent; |
|
||||||
} |
|
||||||
|
|
||||||
size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size_t len) |
|
||||||
{ |
|
||||||
if(!_callback) |
|
||||||
return _fillBuffer(data, len); |
|
||||||
|
|
||||||
const size_t originalLen = len; |
|
||||||
len = _readDataFromCacheOrContent(data, len); |
|
||||||
// Now we've read 'len' bytes, either from cache or from file
|
|
||||||
// Search for template placeholders
|
|
||||||
uint8_t* pTemplateStart = data; |
|
||||||
while((pTemplateStart < &data[len]) && (pTemplateStart = (uint8_t*)memchr(pTemplateStart, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart + 1))) { // data[0] ... data[len - 1]
|
|
||||||
uint8_t* pTemplateEnd = (pTemplateStart < &data[len - 1]) ? (uint8_t*)memchr(pTemplateStart + 1, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart) : nullptr; |
|
||||||
// temporary buffer to hold parameter name
|
|
||||||
uint8_t buf[TEMPLATE_PARAM_NAME_LENGTH + 1]; |
|
||||||
String paramName; |
|
||||||
// If closing placeholder is found:
|
|
||||||
if(pTemplateEnd) { |
|
||||||
// prepare argument to callback
|
|
||||||
const size_t paramNameLength = std::min(sizeof(buf) - 1, (unsigned int)(pTemplateEnd - pTemplateStart - 1)); |
|
||||||
if(paramNameLength) { |
|
||||||
memcpy(buf, pTemplateStart + 1, paramNameLength); |
|
||||||
buf[paramNameLength] = 0; |
|
||||||
paramName = String(reinterpret_cast<char*>(buf)); |
|
||||||
} else { // double percent sign encountered, this is single percent sign escaped.
|
|
||||||
// remove the 2nd percent sign
|
|
||||||
memmove(pTemplateEnd, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1); |
|
||||||
len += _readDataFromCacheOrContent(&data[len - 1], 1) - 1; |
|
||||||
++pTemplateStart; |
|
||||||
} |
|
||||||
} else if(&data[len - 1] - pTemplateStart + 1 < TEMPLATE_PARAM_NAME_LENGTH + 2) { // closing placeholder not found, check if it's in the remaining file data
|
|
||||||
memcpy(buf, pTemplateStart + 1, &data[len - 1] - pTemplateStart); |
|
||||||
const size_t readFromCacheOrContent = _readDataFromCacheOrContent(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PARAM_NAME_LENGTH + 2 - (&data[len - 1] - pTemplateStart + 1)); |
|
||||||
if(readFromCacheOrContent) { |
|
||||||
pTemplateEnd = (uint8_t*)memchr(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PLACEHOLDER, readFromCacheOrContent); |
|
||||||
if(pTemplateEnd) { |
|
||||||
// prepare argument to callback
|
|
||||||
*pTemplateEnd = 0; |
|
||||||
paramName = String(reinterpret_cast<char*>(buf)); |
|
||||||
// Copy remaining read-ahead data into cache
|
|
||||||
_cache.insert(_cache.begin(), pTemplateEnd + 1, buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent); |
|
||||||
pTemplateEnd = &data[len - 1]; |
|
||||||
} |
|
||||||
else // closing placeholder not found in file data, store found percent symbol as is and advance to the next position
|
|
||||||
{ |
|
||||||
// but first, store read file data in cache
|
|
||||||
_cache.insert(_cache.begin(), buf + (&data[len - 1] - pTemplateStart), buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent); |
|
||||||
++pTemplateStart; |
|
||||||
} |
|
||||||
} |
|
||||||
else // closing placeholder not found in content data, store found percent symbol as is and advance to the next position
|
|
||||||
++pTemplateStart; |
|
||||||
} |
|
||||||
else // closing placeholder not found in content data, store found percent symbol as is and advance to the next position
|
|
||||||
++pTemplateStart; |
|
||||||
if(paramName.length()) { |
|
||||||
// call callback and replace with result.
|
|
||||||
// Everything in range [pTemplateStart, pTemplateEnd] can be safely replaced with parameter value.
|
|
||||||
// Data after pTemplateEnd may need to be moved.
|
|
||||||
// The first byte of data after placeholder is located at pTemplateEnd + 1.
|
|
||||||
// It should be located at pTemplateStart + numBytesCopied (to begin right after inserted parameter value).
|
|
||||||
const String paramValue(_callback(paramName)); |
|
||||||
const char* pvstr = paramValue.c_str(); |
|
||||||
const unsigned int pvlen = paramValue.length(); |
|
||||||
const size_t numBytesCopied = std::min(pvlen, static_cast<unsigned int>(&data[originalLen - 1] - pTemplateStart + 1)); |
|
||||||
// make room for param value
|
|
||||||
// 1. move extra data to cache if parameter value is longer than placeholder AND if there is no room to store
|
|
||||||
if((pTemplateEnd + 1 < pTemplateStart + numBytesCopied) && (originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1) < len)) { |
|
||||||
_cache.insert(_cache.begin(), &data[originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1)], &data[len]); |
|
||||||
//2. parameter value is longer than placeholder text, push the data after placeholder which not saved into cache further to the end
|
|
||||||
memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[originalLen] - pTemplateStart - numBytesCopied); |
|
||||||
len = originalLen; // fix issue with truncated data, not sure if it has any side effects
|
|
||||||
} else if(pTemplateEnd + 1 != pTemplateStart + numBytesCopied) |
|
||||||
//2. Either parameter value is shorter than placeholder text OR there is enough free space in buffer to fit.
|
|
||||||
// Move the entire data after the placeholder
|
|
||||||
memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1); |
|
||||||
// 3. replace placeholder with actual value
|
|
||||||
memcpy(pTemplateStart, pvstr, numBytesCopied); |
|
||||||
// If result is longer than buffer, copy the remainder into cache (this could happen only if placeholder text itself did not fit entirely in buffer)
|
|
||||||
if(numBytesCopied < pvlen) { |
|
||||||
_cache.insert(_cache.begin(), pvstr + numBytesCopied, pvstr + pvlen); |
|
||||||
} else if(pTemplateStart + numBytesCopied < pTemplateEnd + 1) { // result is copied fully; if result is shorter than placeholder text...
|
|
||||||
// there is some free room, fill it from cache
|
|
||||||
const size_t roomFreed = pTemplateEnd + 1 - pTemplateStart - numBytesCopied; |
|
||||||
const size_t totalFreeRoom = originalLen - len + roomFreed; |
|
||||||
len += _readDataFromCacheOrContent(&data[len - roomFreed], totalFreeRoom) - roomFreed; |
|
||||||
} else { // result is copied fully; it is longer than placeholder text
|
|
||||||
const size_t roomTaken = pTemplateStart + numBytesCopied - pTemplateEnd - 1; |
|
||||||
len = std::min(len + roomTaken, originalLen); |
|
||||||
} |
|
||||||
} |
|
||||||
} // while(pTemplateStart)
|
|
||||||
return len; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* File Response |
|
||||||
* */ |
|
||||||
|
|
||||||
AsyncFileResponse::~AsyncFileResponse(){ |
|
||||||
if(_content) |
|
||||||
_content.close(); |
|
||||||
} |
|
||||||
|
|
||||||
void AsyncFileResponse::_setContentType(const String& path){ |
|
||||||
if (path.endsWith(".html")) _contentType = "text/html"; |
|
||||||
else if (path.endsWith(".htm")) _contentType = "text/html"; |
|
||||||
else if (path.endsWith(".css")) _contentType = "text/css"; |
|
||||||
else if (path.endsWith(".json")) _contentType = "application/json"; |
|
||||||
else if (path.endsWith(".js")) _contentType = "application/javascript"; |
|
||||||
else if (path.endsWith(".png")) _contentType = "image/png"; |
|
||||||
else if (path.endsWith(".gif")) _contentType = "image/gif"; |
|
||||||
else if (path.endsWith(".jpg")) _contentType = "image/jpeg"; |
|
||||||
else if (path.endsWith(".ico")) _contentType = "image/x-icon"; |
|
||||||
else if (path.endsWith(".svg")) _contentType = "image/svg+xml"; |
|
||||||
else if (path.endsWith(".eot")) _contentType = "font/eot"; |
|
||||||
else if (path.endsWith(".woff")) _contentType = "font/woff"; |
|
||||||
else if (path.endsWith(".woff2")) _contentType = "font/woff2"; |
|
||||||
else if (path.endsWith(".ttf")) _contentType = "font/ttf"; |
|
||||||
else if (path.endsWith(".xml")) _contentType = "text/xml"; |
|
||||||
else if (path.endsWith(".pdf")) _contentType = "application/pdf"; |
|
||||||
else if (path.endsWith(".zip")) _contentType = "application/zip"; |
|
||||||
else if(path.endsWith(".gz")) _contentType = "application/x-gzip"; |
|
||||||
else _contentType = "text/plain"; |
|
||||||
} |
|
||||||
|
|
||||||
AsyncFileResponse::AsyncFileResponse(FS &fs, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback): AsyncAbstractResponse(callback){ |
|
||||||
_code = 200; |
|
||||||
_path = path; |
|
||||||
|
|
||||||
if(!download && !fs.exists(_path) && fs.exists(_path+".gz")){ |
|
||||||
_path = _path+".gz"; |
|
||||||
addHeader("Content-Encoding", "gzip"); |
|
||||||
_callback = nullptr; // Unable to process zipped templates
|
|
||||||
_sendContentLength = true; |
|
||||||
_chunked = false; |
|
||||||
} |
|
||||||
|
|
||||||
_content = fs.open(_path, "r"); |
|
||||||
_contentLength = _content.size(); |
|
||||||
|
|
||||||
if(contentType == "") |
|
||||||
_setContentType(path); |
|
||||||
else |
|
||||||
_contentType = contentType; |
|
||||||
|
|
||||||
int filenameStart = path.lastIndexOf('/') + 1; |
|
||||||
char buf[26+path.length()-filenameStart]; |
|
||||||
char* filename = (char*)path.c_str() + filenameStart; |
|
||||||
|
|
||||||
if(download) { |
|
||||||
// set filename and force download
|
|
||||||
snprintf(buf, sizeof (buf), "attachment; filename=\"%s\"", filename); |
|
||||||
} else { |
|
||||||
// set filename and force rendering
|
|
||||||
snprintf(buf, sizeof (buf), "inline; filename=\"%s\"", filename); |
|
||||||
} |
|
||||||
addHeader("Content-Disposition", buf); |
|
||||||
} |
|
||||||
|
|
||||||
AsyncFileResponse::AsyncFileResponse(File content, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback): AsyncAbstractResponse(callback){ |
|
||||||
_code = 200; |
|
||||||
_path = path; |
|
||||||
|
|
||||||
if(!download && String(content.name()).endsWith(".gz") && !path.endsWith(".gz")){ |
|
||||||
addHeader("Content-Encoding", "gzip"); |
|
||||||
_callback = nullptr; // Unable to process gzipped templates
|
|
||||||
_sendContentLength = true; |
|
||||||
_chunked = false; |
|
||||||
} |
|
||||||
|
|
||||||
_content = content; |
|
||||||
_contentLength = _content.size(); |
|
||||||
|
|
||||||
if(contentType == "") |
|
||||||
_setContentType(path); |
|
||||||
else |
|
||||||
_contentType = contentType; |
|
||||||
|
|
||||||
int filenameStart = path.lastIndexOf('/') + 1; |
|
||||||
char buf[26+path.length()-filenameStart]; |
|
||||||
char* filename = (char*)path.c_str() + filenameStart; |
|
||||||
|
|
||||||
if(download) { |
|
||||||
snprintf(buf, sizeof (buf), "attachment; filename=\"%s\"", filename); |
|
||||||
} else { |
|
||||||
snprintf(buf, sizeof (buf), "inline; filename=\"%s\"", filename); |
|
||||||
} |
|
||||||
addHeader("Content-Disposition", buf); |
|
||||||
} |
|
||||||
|
|
||||||
size_t AsyncFileResponse::_fillBuffer(uint8_t *data, size_t len){ |
|
||||||
return _content.read(data, len); |
|
||||||
} |
|
||||||
|
|
||||||
/*
|
|
||||||
* Stream Response |
|
||||||
* */ |
|
||||||
|
|
||||||
AsyncStreamResponse::AsyncStreamResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback): AsyncAbstractResponse(callback) { |
|
||||||
_code = 200; |
|
||||||
_content = &stream; |
|
||||||
_contentLength = len; |
|
||||||
_contentType = contentType; |
|
||||||
} |
|
||||||
|
|
||||||
size_t AsyncStreamResponse::_fillBuffer(uint8_t *data, size_t len){ |
|
||||||
size_t available = _content->available(); |
|
||||||
size_t outLen = (available > len)?len:available; |
|
||||||
size_t i; |
|
||||||
for(i=0;i<outLen;i++) |
|
||||||
data[i] = _content->read(); |
|
||||||
return outLen; |
|
||||||
} |
|
||||||
|
|
||||||
/*
|
|
||||||
* Callback Response |
|
||||||
* */ |
|
||||||
|
|
||||||
AsyncCallbackResponse::AsyncCallbackResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback): AsyncAbstractResponse(templateCallback) { |
|
||||||
_code = 200; |
|
||||||
_content = callback; |
|
||||||
_contentLength = len; |
|
||||||
if(!len) |
|
||||||
_sendContentLength = false; |
|
||||||
_contentType = contentType; |
|
||||||
_filledLength = 0; |
|
||||||
} |
|
||||||
|
|
||||||
size_t AsyncCallbackResponse::_fillBuffer(uint8_t *data, size_t len){ |
|
||||||
size_t ret = _content(data, len, _filledLength); |
|
||||||
if(ret != RESPONSE_TRY_AGAIN){ |
|
||||||
_filledLength += ret; |
|
||||||
} |
|
||||||
return ret; |
|
||||||
} |
|
||||||
|
|
||||||
/*
|
|
||||||
* Chunked Response |
|
||||||
* */ |
|
||||||
|
|
||||||
AsyncChunkedResponse::AsyncChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor processorCallback): AsyncAbstractResponse(processorCallback) { |
|
||||||
_code = 200; |
|
||||||
_content = callback; |
|
||||||
_contentLength = 0; |
|
||||||
_contentType = contentType; |
|
||||||
_sendContentLength = false; |
|
||||||
_chunked = true; |
|
||||||
_filledLength = 0; |
|
||||||
} |
|
||||||
|
|
||||||
size_t AsyncChunkedResponse::_fillBuffer(uint8_t *data, size_t len){ |
|
||||||
size_t ret = _content(data, len, _filledLength); |
|
||||||
if(ret != RESPONSE_TRY_AGAIN){ |
|
||||||
_filledLength += ret; |
|
||||||
} |
|
||||||
return ret; |
|
||||||
} |
|
||||||
|
|
||||||
/*
|
|
||||||
* Progmem Response |
|
||||||
* */ |
|
||||||
|
|
||||||
AsyncProgmemResponse::AsyncProgmemResponse(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback): AsyncAbstractResponse(callback) { |
|
||||||
_code = code; |
|
||||||
_content = content; |
|
||||||
_contentType = contentType; |
|
||||||
_contentLength = len; |
|
||||||
_readLength = 0; |
|
||||||
} |
|
||||||
|
|
||||||
size_t AsyncProgmemResponse::_fillBuffer(uint8_t *data, size_t len){ |
|
||||||
size_t left = _contentLength - _readLength; |
|
||||||
if (left > len) { |
|
||||||
memcpy_P(data, _content + _readLength, len); |
|
||||||
_readLength += len; |
|
||||||
return len; |
|
||||||
} |
|
||||||
memcpy_P(data, _content + _readLength, left); |
|
||||||
_readLength += left; |
|
||||||
return left; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Response Stream (You can print/write/printf to it, up to the contentLen bytes) |
|
||||||
* */ |
|
||||||
|
|
||||||
AsyncResponseStream::AsyncResponseStream(const String& contentType, size_t bufferSize){ |
|
||||||
_code = 200; |
|
||||||
_contentLength = 0; |
|
||||||
_contentType = contentType; |
|
||||||
_content = new cbuf(bufferSize); |
|
||||||
} |
|
||||||
|
|
||||||
AsyncResponseStream::~AsyncResponseStream(){ |
|
||||||
delete _content; |
|
||||||
} |
|
||||||
|
|
||||||
size_t AsyncResponseStream::_fillBuffer(uint8_t *buf, size_t maxLen){ |
|
||||||
return _content->read((char*)buf, maxLen); |
|
||||||
} |
|
||||||
|
|
||||||
size_t AsyncResponseStream::write(const uint8_t *data, size_t len){ |
|
||||||
if(_started()) |
|
||||||
return 0; |
|
||||||
|
|
||||||
if(len > _content->room()){ |
|
||||||
size_t needed = len - _content->room(); |
|
||||||
_content->resizeAdd(needed); |
|
||||||
} |
|
||||||
size_t written = _content->write((const char*)data, len); |
|
||||||
_contentLength += written; |
|
||||||
return written; |
|
||||||
} |
|
||||||
|
|
||||||
size_t AsyncResponseStream::write(uint8_t data){ |
|
||||||
return write(&data, 1); |
|
||||||
} |
|
@ -1,193 +0,0 @@ |
|||||||
/*
|
|
||||||
Asynchronous WebServer library for Espressif MCUs |
|
||||||
|
|
||||||
Copyright (c) 2016 Hristo Gochkov. All rights reserved. |
|
||||||
This file is part of the esp8266 core for Arduino environment. |
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or |
|
||||||
modify it under the terms of the GNU Lesser General Public |
|
||||||
License as published by the Free Software Foundation; either |
|
||||||
version 2.1 of the License, or (at your option) any later version. |
|
||||||
|
|
||||||
This library is distributed in the hope that it will be useful, |
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
||||||
Lesser General Public License for more details. |
|
||||||
|
|
||||||
You should have received a copy of the GNU Lesser General Public |
|
||||||
License along with this library; if not, write to the Free Software |
|
||||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
|
||||||
*/ |
|
||||||
#include "ESPAsyncWebServer.h" |
|
||||||
#include "WebHandlerImpl.h" |
|
||||||
|
|
||||||
bool ON_STA_FILTER(AsyncWebServerRequest *request) { |
|
||||||
return WiFi.localIP() == request->client()->localIP(); |
|
||||||
} |
|
||||||
|
|
||||||
bool ON_AP_FILTER(AsyncWebServerRequest *request) { |
|
||||||
return WiFi.localIP() != request->client()->localIP(); |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
AsyncWebServer::AsyncWebServer(uint16_t port) |
|
||||||
: _server(port) |
|
||||||
, _rewrites(LinkedList<AsyncWebRewrite*>([](AsyncWebRewrite* r){ delete r; })) |
|
||||||
, _handlers(LinkedList<AsyncWebHandler*>([](AsyncWebHandler* h){ delete h; })) |
|
||||||
{ |
|
||||||
_catchAllHandler = new AsyncCallbackWebHandler(); |
|
||||||
if(_catchAllHandler == NULL) |
|
||||||
return; |
|
||||||
_server.onClient([](void *s, AsyncClient* c){ |
|
||||||
if(c == NULL) |
|
||||||
return; |
|
||||||
c->setRxTimeout(3); |
|
||||||
AsyncWebServerRequest *r = new AsyncWebServerRequest((AsyncWebServer*)s, c); |
|
||||||
if(r == NULL){ |
|
||||||
c->close(true); |
|
||||||
c->free(); |
|
||||||
delete c; |
|
||||||
} |
|
||||||
}, this); |
|
||||||
} |
|
||||||
|
|
||||||
AsyncWebServer::~AsyncWebServer(){ |
|
||||||
reset();
|
|
||||||
end(); |
|
||||||
if(_catchAllHandler) delete _catchAllHandler; |
|
||||||
} |
|
||||||
|
|
||||||
AsyncWebRewrite& AsyncWebServer::addRewrite(AsyncWebRewrite* rewrite){ |
|
||||||
_rewrites.add(rewrite); |
|
||||||
return *rewrite; |
|
||||||
} |
|
||||||
|
|
||||||
bool AsyncWebServer::removeRewrite(AsyncWebRewrite *rewrite){ |
|
||||||
return _rewrites.remove(rewrite); |
|
||||||
} |
|
||||||
|
|
||||||
AsyncWebRewrite& AsyncWebServer::rewrite(const char* from, const char* to){ |
|
||||||
return addRewrite(new AsyncWebRewrite(from, to)); |
|
||||||
} |
|
||||||
|
|
||||||
AsyncWebHandler& AsyncWebServer::addHandler(AsyncWebHandler* handler){ |
|
||||||
_handlers.add(handler); |
|
||||||
return *handler; |
|
||||||
} |
|
||||||
|
|
||||||
bool AsyncWebServer::removeHandler(AsyncWebHandler *handler){ |
|
||||||
return _handlers.remove(handler); |
|
||||||
} |
|
||||||
|
|
||||||
void AsyncWebServer::begin(){ |
|
||||||
_server.setNoDelay(true); |
|
||||||
_server.begin(); |
|
||||||
} |
|
||||||
|
|
||||||
void AsyncWebServer::end(){ |
|
||||||
_server.end(); |
|
||||||
} |
|
||||||
|
|
||||||
#if ASYNC_TCP_SSL_ENABLED |
|
||||||
void AsyncWebServer::onSslFileRequest(AcSSlFileHandler cb, void* arg){ |
|
||||||
_server.onSslFileRequest(cb, arg); |
|
||||||
} |
|
||||||
|
|
||||||
void AsyncWebServer::beginSecure(const char *cert, const char *key, const char *password){ |
|
||||||
_server.beginSecure(cert, key, password); |
|
||||||
} |
|
||||||
#endif |
|
||||||
|
|
||||||
void AsyncWebServer::_handleDisconnect(AsyncWebServerRequest *request){ |
|
||||||
delete request; |
|
||||||
} |
|
||||||
|
|
||||||
void AsyncWebServer::_rewriteRequest(AsyncWebServerRequest *request){ |
|
||||||
for(const auto& r: _rewrites){ |
|
||||||
if (r->match(request)){ |
|
||||||
request->_url = r->toUrl(); |
|
||||||
request->_addGetParams(r->params()); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
void AsyncWebServer::_attachHandler(AsyncWebServerRequest *request){ |
|
||||||
for(const auto& h: _handlers){ |
|
||||||
if (h->filter(request) && h->canHandle(request)){ |
|
||||||
request->setHandler(h); |
|
||||||
return; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
request->addInterestingHeader("ANY"); |
|
||||||
request->setHandler(_catchAllHandler); |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody){ |
|
||||||
AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler(); |
|
||||||
handler->setUri(uri); |
|
||||||
handler->setMethod(method); |
|
||||||
handler->onRequest(onRequest); |
|
||||||
handler->onUpload(onUpload); |
|
||||||
handler->onBody(onBody); |
|
||||||
addHandler(handler); |
|
||||||
return *handler; |
|
||||||
} |
|
||||||
|
|
||||||
AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload){ |
|
||||||
AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler(); |
|
||||||
handler->setUri(uri); |
|
||||||
handler->setMethod(method); |
|
||||||
handler->onRequest(onRequest); |
|
||||||
handler->onUpload(onUpload); |
|
||||||
addHandler(handler); |
|
||||||
return *handler; |
|
||||||
} |
|
||||||
|
|
||||||
AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest){ |
|
||||||
AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler(); |
|
||||||
handler->setUri(uri); |
|
||||||
handler->setMethod(method); |
|
||||||
handler->onRequest(onRequest); |
|
||||||
addHandler(handler); |
|
||||||
return *handler; |
|
||||||
} |
|
||||||
|
|
||||||
AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, ArRequestHandlerFunction onRequest){ |
|
||||||
AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler(); |
|
||||||
handler->setUri(uri); |
|
||||||
handler->onRequest(onRequest); |
|
||||||
addHandler(handler); |
|
||||||
return *handler; |
|
||||||
} |
|
||||||
|
|
||||||
AsyncStaticWebHandler& AsyncWebServer::serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_control){ |
|
||||||
AsyncStaticWebHandler* handler = new AsyncStaticWebHandler(uri, fs, path, cache_control); |
|
||||||
addHandler(handler); |
|
||||||
return *handler; |
|
||||||
} |
|
||||||
|
|
||||||
void AsyncWebServer::onNotFound(ArRequestHandlerFunction fn){ |
|
||||||
_catchAllHandler->onRequest(fn); |
|
||||||
} |
|
||||||
|
|
||||||
void AsyncWebServer::onFileUpload(ArUploadHandlerFunction fn){ |
|
||||||
_catchAllHandler->onUpload(fn); |
|
||||||
} |
|
||||||
|
|
||||||
void AsyncWebServer::onRequestBody(ArBodyHandlerFunction fn){ |
|
||||||
_catchAllHandler->onBody(fn); |
|
||||||
} |
|
||||||
|
|
||||||
void AsyncWebServer::reset(){ |
|
||||||
_rewrites.free(); |
|
||||||
_handlers.free(); |
|
||||||
|
|
||||||
if (_catchAllHandler != NULL){ |
|
||||||
_catchAllHandler->onRequest(NULL); |
|
||||||
_catchAllHandler->onUpload(NULL); |
|
||||||
_catchAllHandler->onBody(NULL); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
@ -1,627 +0,0 @@ |
|||||||
<!--This is the plain html source of the hex encoded Editor-Page embedded in SPIFFSEditor.cpp --> |
|
||||||
<!DOCTYPE html> |
|
||||||
<html lang="en"> |
|
||||||
<head> |
|
||||||
<title>ESP Editor</title> |
|
||||||
<style type="text/css" media="screen"> |
|
||||||
.cm { |
|
||||||
z-index: 300; |
|
||||||
position: absolute; |
|
||||||
left: 5px; |
|
||||||
border: 1px solid #444; |
|
||||||
background-color: #F5F5F5; |
|
||||||
display: none; |
|
||||||
box-shadow: 0 0 10px rgba( 0, 0, 0, .4 ); |
|
||||||
font-size: 12px; |
|
||||||
font-family: sans-serif; |
|
||||||
font-weight:bold; |
|
||||||
} |
|
||||||
.cm ul { |
|
||||||
list-style: none; |
|
||||||
top: 0; |
|
||||||
left: 0; |
|
||||||
margin: 0; |
|
||||||
padding: 0; |
|
||||||
} |
|
||||||
.cm li { |
|
||||||
position: relative; |
|
||||||
min-width: 60px; |
|
||||||
cursor: pointer; |
|
||||||
} |
|
||||||
.cm span { |
|
||||||
color: #444; |
|
||||||
display: inline-block; |
|
||||||
padding: 6px; |
|
||||||
} |
|
||||||
.cm li:hover { background: #444; } |
|
||||||
.cm li:hover span { color: #EEE; } |
|
||||||
.tvu ul, .tvu li { |
|
||||||
padding: 0; |
|
||||||
margin: 0; |
|
||||||
list-style: none; |
|
||||||
} |
|
||||||
.tvu input { |
|
||||||
position: absolute; |
|
||||||
opacity: 0; |
|
||||||
} |
|
||||||
.tvu { |
|
||||||
font: normal 12px Verdana, Arial, Sans-serif; |
|
||||||
-moz-user-select: none; |
|
||||||
-webkit-user-select: none; |
|
||||||
user-select: none; |
|
||||||
color: #444; |
|
||||||
line-height: 16px; |
|
||||||
} |
|
||||||
.tvu span { |
|
||||||
margin-bottom:5px; |
|
||||||
padding: 0 0 0 18px; |
|
||||||
cursor: pointer; |
|
||||||
display: inline-block; |
|
||||||
height: 16px; |
|
||||||
vertical-align: middle; |
|
||||||
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAADoSURBVBgZBcExblNBGAbA2ceegTRBuIKOgiihSZNTcC5LUHAihNJR0kGKCDcYJY6D3/77MdOinTvzAgCw8ysThIvn/VojIyMjIyPP+bS1sUQIV2s95pBDDvmbP/mdkft83tpYguZq5Jh/OeaYh+yzy8hTHvNlaxNNczm+la9OTlar1UdA/+C2A4trRCnD3jS8BB1obq2Gk6GU6QbQAS4BUaYSQAf4bhhKKTFdAzrAOwAxEUAH+KEM01SY3gM6wBsEAQB0gJ+maZoC3gI6iPYaAIBJsiRmHU0AALOeFC3aK2cWAACUXe7+AwO0lc9eTHYTAAAAAElFTkSuQmCC') no-repeat; |
|
||||||
background-position: 0px 0px; |
|
||||||
} |
|
||||||
.tvu span:hover { |
|
||||||
text-decoration: underline; |
|
||||||
} |
|
||||||
@media screen and (-webkit-min-device-pixel-ratio:0){ |
|
||||||
.tvu{ |
|
||||||
-webkit-animation: webkit-adjacent-element-selector-bugfix infinite 1s; |
|
||||||
} |
|
||||||
|
|
||||||
@-webkit-keyframes webkit-adjacent-element-selector-bugfix { |
|
||||||
from { |
|
||||||
padding: 0; |
|
||||||
} |
|
||||||
to { |
|
||||||
padding: 0; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
#uploader { |
|
||||||
position: absolute; |
|
||||||
top: 0; |
|
||||||
right: 0; |
|
||||||
left: 0; |
|
||||||
height:28px; |
|
||||||
line-height: 24px; |
|
||||||
padding-left: 10px; |
|
||||||
background-color: #444; |
|
||||||
color:#EEE; |
|
||||||
} |
|
||||||
#tree { |
|
||||||
position: absolute; |
|
||||||
top: 28px; |
|
||||||
bottom: 0; |
|
||||||
left: 0; |
|
||||||
width:160px; |
|
||||||
padding: 8px; |
|
||||||
} |
|
||||||
#editor, #preview { |
|
||||||
position: absolute; |
|
||||||
top: 28px; |
|
||||||
right: 0; |
|
||||||
bottom: 0; |
|
||||||
left: 160px; |
|
||||||
border-left:1px solid #EEE; |
|
||||||
} |
|
||||||
#preview { |
|
||||||
background-color: #EEE; |
|
||||||
padding:5px; |
|
||||||
} |
|
||||||
#loader { |
|
||||||
position: absolute; |
|
||||||
top: 36%; |
|
||||||
right: 40%; |
|
||||||
} |
|
||||||
.loader { |
|
||||||
z-index: 10000; |
|
||||||
border: 8px solid #b5b5b5; /* Grey */ |
|
||||||
border-top: 8px solid #3498db; /* Blue */ |
|
||||||
border-bottom: 8px solid #3498db; /* Blue */ |
|
||||||
border-radius: 50%; |
|
||||||
width: 240px; |
|
||||||
height: 240px; |
|
||||||
animation: spin 2s linear infinite; |
|
||||||
display:none; |
|
||||||
} |
|
||||||
|
|
||||||
@keyframes spin { |
|
||||||
0% { transform: rotate(0deg); } |
|
||||||
100% { transform: rotate(360deg); } |
|
||||||
} |
|
||||||
</style> |
|
||||||
<script> |
|
||||||
if (typeof XMLHttpRequest === "undefined") { |
|
||||||
XMLHttpRequest = function () { |
|
||||||
try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch (e) {} |
|
||||||
try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch (e) {} |
|
||||||
try { return new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) {} |
|
||||||
throw new Error("This browser does not support XMLHttpRequest."); |
|
||||||
}; |
|
||||||
} |
|
||||||
|
|
||||||
function ge(a){ |
|
||||||
return document.getElementById(a); |
|
||||||
} |
|
||||||
function ce(a){ |
|
||||||
return document.createElement(a); |
|
||||||
} |
|
||||||
|
|
||||||
function sortByKey(array, key) { |
|
||||||
return array.sort(function(a, b) { |
|
||||||
var x = a[key]; var y = b[key]; |
|
||||||
return ((x < y) ? -1 : ((x > y) ? 1 : 0)); |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
var QueuedRequester = function () { |
|
||||||
this.queue = []; |
|
||||||
this.running = false; |
|
||||||
this.xmlhttp = null; |
|
||||||
} |
|
||||||
QueuedRequester.prototype = { |
|
||||||
_request: function(req){ |
|
||||||
this.running = true; |
|
||||||
if(!req instanceof Object) return; |
|
||||||
var that = this; |
|
||||||
|
|
||||||
function ajaxCb(x,d){ return function(){ |
|
||||||
if (x.readyState == 4){ |
|
||||||
ge("loader").style.display = "none"; |
|
||||||
d.callback(x.status, x.responseText); |
|
||||||
if(that.queue.length === 0) that.running = false; |
|
||||||
if(that.running) that._request(that.queue.shift()); |
|
||||||
} |
|
||||||
}} |
|
||||||
|
|
||||||
ge("loader").style.display = "block"; |
|
||||||
|
|
||||||
var p = ""; |
|
||||||
if(req.params instanceof FormData){ |
|
||||||
p = req.params; |
|
||||||
} else if(req.params instanceof Object){ |
|
||||||
for (var key in req.params) { |
|
||||||
if(p === "") |
|
||||||
p += (req.method === "GET")?"?":""; |
|
||||||
else |
|
||||||
p += "&"; |
|
||||||
p += encodeURIComponent(key)+"="+encodeURIComponent(req.params[key]); |
|
||||||
}; |
|
||||||
} |
|
||||||
|
|
||||||
this.xmlhttp = new XMLHttpRequest(); |
|
||||||
this.xmlhttp.onreadystatechange = ajaxCb(this.xmlhttp, req); |
|
||||||
if(req.method === "GET"){ |
|
||||||
this.xmlhttp.open(req.method, req.url+p, true); |
|
||||||
this.xmlhttp.send(); |
|
||||||
} else { |
|
||||||
this.xmlhttp.open(req.method, req.url, true); |
|
||||||
if(p instanceof String) |
|
||||||
this.xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); |
|
||||||
this.xmlhttp.send(p); |
|
||||||
} |
|
||||||
}, |
|
||||||
stop: function(){ |
|
||||||
if(this.running) this.running = false; |
|
||||||
if(this.xmlhttp && this.xmlhttp.readyState < 4){ |
|
||||||
this.xmlhttp.abort(); |
|
||||||
} |
|
||||||
}, |
|
||||||
add: function(method, url, params, callback){ |
|
||||||
this.queue.push({url:url,method:method,params:params,callback:callback}); |
|
||||||
if(!this.running){ |
|
||||||
this._request(this.queue.shift()); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
var requests = new QueuedRequester(); |
|
||||||
|
|
||||||
function createFileUploader(element, tree, editor){ |
|
||||||
var xmlHttp; |
|
||||||
|
|
||||||
var refresh = ce("button"); |
|
||||||
refresh.innerHTML = 'Refresh List'; |
|
||||||
ge(element).appendChild(refresh); |
|
||||||
|
|
||||||
var input = ce("input"); |
|
||||||
input.type = "file"; |
|
||||||
input.multiple = false; |
|
||||||
input.name = "data"; |
|
||||||
input.id="upload-select"; |
|
||||||
ge(element).appendChild(input); |
|
||||||
|
|
||||||
var path = ce("input"); |
|
||||||
path.id = "upload-path"; |
|
||||||
path.type = "text"; |
|
||||||
path.name = "path"; |
|
||||||
path.defaultValue = "/"; |
|
||||||
ge(element).appendChild(path); |
|
||||||
|
|
||||||
var button = ce("button"); |
|
||||||
button.innerHTML = 'Upload'; |
|
||||||
ge(element).appendChild(button); |
|
||||||
|
|
||||||
var mkfile = ce("button"); |
|
||||||
mkfile.innerHTML = 'Create'; |
|
||||||
ge(element).appendChild(mkfile); |
|
||||||
|
|
||||||
var filename = ce("input"); |
|
||||||
filename.id = "editor-filename"; |
|
||||||
filename.type = "text"; |
|
||||||
filename.disabled= true; |
|
||||||
filename.size = 20; |
|
||||||
ge(element).appendChild(filename); |
|
||||||
|
|
||||||
var savefile = ce("button"); |
|
||||||
savefile.innerHTML = ' Save ' ; |
|
||||||
ge(element).appendChild(savefile); |
|
||||||
|
|
||||||
function httpPostProcessRequest(status, responseText){ |
|
||||||
if(status != 200) |
|
||||||
alert("ERROR["+status+"]: "+responseText); |
|
||||||
else |
|
||||||
tree.refreshPath(path.value); |
|
||||||
} |
|
||||||
function createPath(p){ |
|
||||||
var formData = new FormData(); |
|
||||||
formData.append("path", p); |
|
||||||
requests.add("PUT", "/edit", formData, httpPostProcessRequest); |
|
||||||
} |
|
||||||
|
|
||||||
mkfile.onclick = function(e){ |
|
||||||
createPath(path.value); |
|
||||||
editor.loadUrl(path.value); |
|
||||||
path.value="/"; |
|
||||||
}; |
|
||||||
|
|
||||||
savefile.onclick = function(e){ |
|
||||||
editor.execCommand('saveCommand'); |
|
||||||
}; |
|
||||||
|
|
||||||
refresh.onclick = function(e){ |
|
||||||
tree.refreshPath(path.value); |
|
||||||
}; |
|
||||||
|
|
||||||
button.onclick = function(e){ |
|
||||||
if(input.files.length === 0){ |
|
||||||
return; |
|
||||||
} |
|
||||||
var formData = new FormData(); |
|
||||||
formData.append("data", input.files[0], path.value); |
|
||||||
requests.add("POST", "/edit", formData, httpPostProcessRequest); |
|
||||||
var uploadPath= ge("upload-path"); |
|
||||||
uploadPath.value="/"; |
|
||||||
var uploadSelect= ge("upload-select"); |
|
||||||
uploadSelect.value=""; |
|
||||||
}; |
|
||||||
input.onchange = function(e){ |
|
||||||
if(input.files.length === 0) return; |
|
||||||
var filename = input.files[0].name; |
|
||||||
var ext = /(?:\.([^.]+))?$/.exec(filename)[1]; |
|
||||||
var name = /(.*)\.[^.]+$/.exec(filename)[1]; |
|
||||||
if(typeof name !== undefined){ |
|
||||||
filename = name; |
|
||||||
} |
|
||||||
path.value = "/"+filename+"."+ext; |
|
||||||
}; |
|
||||||
} |
|
||||||
|
|
||||||
function createTree(element, editor){ |
|
||||||
var preview = ge("preview"); |
|
||||||
var treeRoot = ce("div"); |
|
||||||
treeRoot.className = "tvu"; |
|
||||||
ge(element).appendChild(treeRoot); |
|
||||||
|
|
||||||
function loadDownload(path){ |
|
||||||
ge('download-frame').src = "/edit?download="+path; |
|
||||||
} |
|
||||||
|
|
||||||
function loadPreview(path){ |
|
||||||
var edfname = ge("editor-filename"); |
|
||||||
edfname.value=path; |
|
||||||
ge("editor").style.display = "none"; |
|
||||||
preview.style.display = "block"; |
|
||||||
preview.innerHTML = '<img src="/edit?edit='+path+'&_cb='+Date.now()+'" style="max-width:100%; max-height:100%; margin:auto; display:block;" />'; |
|
||||||
} |
|
||||||
|
|
||||||
function fillFileMenu(el, path){ |
|
||||||
var list = ce("ul"); |
|
||||||
el.appendChild(list); |
|
||||||
var action = ce("li"); |
|
||||||
list.appendChild(action); |
|
||||||
if(isImageFile(path)){ |
|
||||||
action.innerHTML = "<span>Preview</span>"; |
|
||||||
action.onclick = function(e){ |
|
||||||
loadPreview(path); |
|
||||||
if(document.body.getElementsByClassName('cm').length > 0) document.body.removeChild(el); |
|
||||||
}; |
|
||||||
} else if(isTextFile(path)){ |
|
||||||
action.innerHTML = "<span>Edit</span>"; |
|
||||||
action.onclick = function(e){ |
|
||||||
editor.loadUrl(path); |
|
||||||
if(document.body.getElementsByClassName('cm').length > 0) document.body.removeChild(el); |
|
||||||
}; |
|
||||||
} |
|
||||||
var download = ce("li"); |
|
||||||
list.appendChild(download); |
|
||||||
download.innerHTML = "<span>Download</span>"; |
|
||||||
download.onclick = function(e){ |
|
||||||
loadDownload(path); |
|
||||||
if(document.body.getElementsByClassName('cm').length > 0) document.body.removeChild(el); |
|
||||||
}; |
|
||||||
var delFile = ce("li"); |
|
||||||
list.appendChild(delFile); |
|
||||||
delFile.innerHTML = "<span>Delete</span>"; |
|
||||||
delFile.onclick = function(e){ |
|
||||||
httpDelete(path); |
|
||||||
if(document.body.getElementsByClassName('cm').length > 0) document.body.removeChild(el); |
|
||||||
}; |
|
||||||
} |
|
||||||
|
|
||||||
function showContextMenu(event, path, isfile){ |
|
||||||
var divContext = ce("div"); |
|
||||||
var scrollTop = document.body.scrollTop ? document.body.scrollTop : document.documentElement.scrollTop; |
|
||||||
var scrollLeft = document.body.scrollLeft ? document.body.scrollLeft : document.documentElement.scrollLeft; |
|
||||||
var left = event.clientX + scrollLeft; |
|
||||||
var top = event.clientY + scrollTop; |
|
||||||
divContext.className = 'cm'; |
|
||||||
divContext.style.display = 'block'; |
|
||||||
divContext.style.left = left + 'px'; |
|
||||||
divContext.style.top = top + 'px'; |
|
||||||
fillFileMenu(divContext, path); |
|
||||||
document.body.appendChild(divContext); |
|
||||||
var width = divContext.offsetWidth; |
|
||||||
var height = divContext.offsetHeight; |
|
||||||
divContext.onmouseout = function(e){ |
|
||||||
if(e.clientX < left || e.clientX > (left + width) || e.clientY < top || e.clientY > (top + height)){ |
|
||||||
if(document.body.getElementsByClassName('cm').length > 0) document.body.removeChild(divContext); |
|
||||||
} |
|
||||||
}; |
|
||||||
} |
|
||||||
|
|
||||||
function createTreeLeaf(path, name, size){ |
|
||||||
var leaf = ce("li"); |
|
||||||
leaf.id = name; |
|
||||||
var label = ce("span"); |
|
||||||
label.innerHTML = name; |
|
||||||
leaf.appendChild(label); |
|
||||||
leaf.onclick = function(e){ |
|
||||||
if(isTextFile(leaf.id.toLowerCase())){ |
|
||||||
editor.loadUrl(leaf.id); |
|
||||||
} else if(isImageFile(leaf.id.toLowerCase())){ |
|
||||||
loadPreview(leaf.id); |
|
||||||
} |
|
||||||
}; |
|
||||||
leaf.oncontextmenu = function(e){ |
|
||||||
e.preventDefault(); |
|
||||||
e.stopPropagation(); |
|
||||||
showContextMenu(e, leaf.id, true); |
|
||||||
}; |
|
||||||
return leaf; |
|
||||||
} |
|
||||||
|
|
||||||
function addList(parent, path, items){ |
|
||||||
sortByKey(items, 'name'); |
|
||||||
var list = ce("ul"); |
|
||||||
parent.appendChild(list); |
|
||||||
var ll = items.length; |
|
||||||
for(var i = 0; i < ll; i++){ |
|
||||||
if(items[i].type === "file") |
|
||||||
list.appendChild(createTreeLeaf(path, items[i].name, items[i].size)); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
function isTextFile(path){ |
|
||||||
var ext = /(?:\.([^.]+))?$/.exec(path)[1]; |
|
||||||
if(typeof ext !== undefined){ |
|
||||||
switch(ext){ |
|
||||||
case "txt": |
|
||||||
case "htm": |
|
||||||
case "html": |
|
||||||
case "js": |
|
||||||
case "css": |
|
||||||
case "xml": |
|
||||||
case "json": |
|
||||||
case "conf": |
|
||||||
case "ini": |
|
||||||
case "h": |
|
||||||
case "c": |
|
||||||
case "cpp": |
|
||||||
case "php": |
|
||||||
case "hex": |
|
||||||
case "ino": |
|
||||||
case "pde": |
|
||||||
return true; |
|
||||||
} |
|
||||||
} |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
function isImageFile(path){ |
|
||||||
var ext = /(?:\.([^.]+))?$/.exec(path)[1]; |
|
||||||
if(typeof ext !== undefined){ |
|
||||||
switch(ext){ |
|
||||||
case "png": |
|
||||||
case "jpg": |
|
||||||
case "gif": |
|
||||||
case "bmp": |
|
||||||
return true; |
|
||||||
} |
|
||||||
} |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
this.refreshPath = function(path){ |
|
||||||
treeRoot.removeChild(treeRoot.childNodes[0]); |
|
||||||
httpGet(treeRoot, "/"); |
|
||||||
}; |
|
||||||
|
|
||||||
function delCb(path){ |
|
||||||
return function(status, responseText){ |
|
||||||
if(status != 200){ |
|
||||||
alert("ERROR["+status+"]: "+responseText); |
|
||||||
} else { |
|
||||||
treeRoot.removeChild(treeRoot.childNodes[0]); |
|
||||||
httpGet(treeRoot, "/"); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
function httpDelete(filename){ |
|
||||||
var formData = new FormData(); |
|
||||||
formData.append("path", filename); |
|
||||||
requests.add("DELETE", "/edit", formData, delCb(filename)); |
|
||||||
} |
|
||||||
|
|
||||||
function getCb(parent, path){ |
|
||||||
return function(status, responseText){ |
|
||||||
if(status == 200) |
|
||||||
addList(parent, path, JSON.parse(responseText)); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
function httpGet(parent, path){ |
|
||||||
requests.add("GET", "/edit", { list: path }, getCb(parent, path)); |
|
||||||
} |
|
||||||
|
|
||||||
httpGet(treeRoot, "/"); |
|
||||||
return this; |
|
||||||
} |
|
||||||
|
|
||||||
function createEditor(element, file, lang, theme, type){ |
|
||||||
function getLangFromFilename(filename){ |
|
||||||
var lang = "plain"; |
|
||||||
var ext = /(?:\.([^.]+))?$/.exec(filename)[1]; |
|
||||||
if(typeof ext !== undefined){ |
|
||||||
switch(ext){ |
|
||||||
case "txt": lang = "plain"; break; |
|
||||||
case "hex": lang = "plain"; break; |
|
||||||
case "conf": lang = "plain"; break; |
|
||||||
case "htm": lang = "html"; break; |
|
||||||
case "js": lang = "javascript"; break; |
|
||||||
case "h": lang = "c_cpp"; break; |
|
||||||
case "c": lang = "c_cpp"; break; |
|
||||||
case "cpp": lang = "c_cpp"; break; |
|
||||||
case "css": |
|
||||||
case "scss": |
|
||||||
case "php": |
|
||||||
case "html": |
|
||||||
case "json": |
|
||||||
case "xml": |
|
||||||
case "ini": lang = ext; |
|
||||||
} |
|
||||||
} |
|
||||||
return lang; |
|
||||||
} |
|
||||||
|
|
||||||
if(typeof file === "undefined") file = "/index.html"; |
|
||||||
|
|
||||||
if(typeof lang === "undefined"){ |
|
||||||
lang = getLangFromFilename(file); |
|
||||||
} |
|
||||||
|
|
||||||
if(typeof theme === "undefined") theme = "textmate"; |
|
||||||
|
|
||||||
if(typeof type === "undefined"){ |
|
||||||
type = "text/"+lang; |
|
||||||
if(lang === "c_cpp") type = "text/plain"; |
|
||||||
} |
|
||||||
|
|
||||||
var editor = ace.edit(element); |
|
||||||
function httpPostProcessRequest(status, responseText){ |
|
||||||
if(status != 200) alert("ERROR["+status+"]: "+responseText); |
|
||||||
} |
|
||||||
function httpPost(filename, data, type){ |
|
||||||
var formData = new FormData(); |
|
||||||
formData.append("data", new Blob([data], { type: type }), filename); |
|
||||||
requests.add("POST", "/edit", formData, httpPostProcessRequest); |
|
||||||
} |
|
||||||
function httpGetProcessRequest(status, responseText){ |
|
||||||
ge("preview").style.display = "none"; |
|
||||||
ge("editor").style.display = "block"; |
|
||||||
if(status == 200) |
|
||||||
editor.setValue(responseText); |
|
||||||
else |
|
||||||
editor.setValue(""); |
|
||||||
editor.clearSelection(); |
|
||||||
} |
|
||||||
function httpGet(theUrl){ |
|
||||||
requests.add("GET", "/edit", { edit: theUrl }, httpGetProcessRequest); |
|
||||||
} |
|
||||||
|
|
||||||
if(lang !== "plain") editor.getSession().setMode("ace/mode/"+lang); |
|
||||||
editor.setTheme("ace/theme/"+theme); |
|
||||||
editor.$blockScrolling = Infinity; |
|
||||||
editor.getSession().setUseSoftTabs(true); |
|
||||||
editor.getSession().setTabSize(2); |
|
||||||
editor.setHighlightActiveLine(true); |
|
||||||
editor.setShowPrintMargin(false); |
|
||||||
editor.commands.addCommand({ |
|
||||||
name: 'saveCommand', |
|
||||||
bindKey: {win: 'Ctrl-S', mac: 'Command-S'}, |
|
||||||
exec: function(editor) { |
|
||||||
httpPost(file, editor.getValue()+"", type); |
|
||||||
}, |
|
||||||
readOnly: false |
|
||||||
}); |
|
||||||
editor.commands.addCommand({ |
|
||||||
name: 'undoCommand', |
|
||||||
bindKey: {win: 'Ctrl-Z', mac: 'Command-Z'}, |
|
||||||
exec: function(editor) { |
|
||||||
editor.getSession().getUndoManager().undo(false); |
|
||||||
}, |
|
||||||
readOnly: false |
|
||||||
}); |
|
||||||
editor.commands.addCommand({ |
|
||||||
name: 'redoCommand', |
|
||||||
bindKey: {win: 'Ctrl-Shift-Z', mac: 'Command-Shift-Z'}, |
|
||||||
exec: function(editor) { |
|
||||||
editor.getSession().getUndoManager().redo(false); |
|
||||||
}, |
|
||||||
readOnly: false |
|
||||||
}); |
|
||||||
editor.loadUrl = function(filename){ |
|
||||||
var edfname = ge("editor-filename"); |
|
||||||
edfname.value=filename; |
|
||||||
file = filename; |
|
||||||
lang = getLangFromFilename(file); |
|
||||||
type = "text/"+lang; |
|
||||||
if(lang !== "plain") editor.getSession().setMode("ace/mode/"+lang); |
|
||||||
httpGet(file); |
|
||||||
}; |
|
||||||
return editor; |
|
||||||
} |
|
||||||
function onBodyLoad(){ |
|
||||||
var vars = {}; |
|
||||||
var parts = window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, function(m,key,value) { vars[key] = value; }); |
|
||||||
var editor = createEditor("editor", vars.file, vars.lang, vars.theme); |
|
||||||
var tree = createTree("tree", editor); |
|
||||||
createFileUploader("uploader", tree, editor); |
|
||||||
if(typeof vars.file === "undefined") vars.file = "/index.htm"; |
|
||||||
editor.loadUrl(vars.file); |
|
||||||
}; |
|
||||||
</script> |
|
||||||
<script id='ace' src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.6/ace.js" type="text/javascript" charset="utf-8"></script> |
|
||||||
<script> |
|
||||||
if (typeof ace.edit == "undefined") { |
|
||||||
var script = document.createElement('script'); |
|
||||||
script.src = "/ace.js"; |
|
||||||
script.async = false; |
|
||||||
document.head.appendChild(script); |
|
||||||
} |
|
||||||
</script> |
|
||||||
</head> |
|
||||||
<body onload="onBodyLoad();"> |
|
||||||
<div id="loader" class="loader"></div> |
|
||||||
<div id="uploader"></div> |
|
||||||
<div id="tree"></div> |
|
||||||
<div id="editor"></div> |
|
||||||
<div id="preview" style="display:none;"></div> |
|
||||||
<iframe id=download-frame style='display:none;'></iframe> |
|
||||||
</body> |
|
||||||
</html> |
|
@ -0,0 +1,25 @@ |
|||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <cstdint> |
||||||
|
|
||||||
|
struct Status |
||||||
|
{ |
||||||
|
char realtime[32] = {0}; // UTC date and time, in format YYYY-MM-DDTHH:mm:ss.sssZ
|
||||||
|
|
||||||
|
uint16_t batteryVoltage = 0; // in mV
|
||||||
|
uint16_t batteryOutputCurrent = 0; // in mV
|
||||||
|
int16_t temperature = 0; // in tenth of °C
|
||||||
|
int32_t altitude = 0; // in mm above sea level (can be negative if below sea level, or depending on atmospheric conditions)
|
||||||
|
float latitude = -1000.0f; // in decimal degrees
|
||||||
|
float longitude = -1000.0f; // in decimal degrees
|
||||||
|
float gpsAltitude = -1000.0f; // in meters, above sea level
|
||||||
|
|
||||||
|
float speed = 0.0f; |
||||||
|
|
||||||
|
// current trip
|
||||||
|
uint32_t tripDistance = 0; // in meters
|
||||||
|
uint16_t tripMovingTime = 0; // cumulated seconds, only when moving at non-zero speed
|
||||||
|
uint16_t tripTotalTime = 0; // total trip time in seconds
|
||||||
|
uint32_t tripAscendingElevation = 0; // cumulated ascending elevation, in millimeters
|
||||||
|
uint32_t tripMotorEnergy = 0; // in Joules
|
||||||
|
}; |
@ -0,0 +1,175 @@ |
|||||||
|
#include "WebServer.h" |
||||||
|
|
||||||
|
#include "DebugLog.h" |
||||||
|
#include "DataLogger.h" |
||||||
|
|
||||||
|
#include <Arduino.h> |
||||||
|
#include <SPIFFS.h> |
||||||
|
#include <string> |
||||||
|
|
||||||
|
#include "HTTPServer.hpp" |
||||||
|
#include "SSLCert.hpp" |
||||||
|
#include "HTTPRequest.hpp" |
||||||
|
#include "HTTPResponse.hpp" |
||||||
|
|
||||||
|
/** Check if we have multiple cores */ |
||||||
|
#if CONFIG_FREERTOS_UNICORE |
||||||
|
#define WEBSERVER_RUNNING_CORE 0 |
||||||
|
#else |
||||||
|
#define WEBSERVER_RUNNING_CORE 1 |
||||||
|
#endif |
||||||
|
|
||||||
|
using namespace httpsserver; |
||||||
|
|
||||||
|
detail::WebServer WebServer; |
||||||
|
|
||||||
|
namespace detail |
||||||
|
{ |
||||||
|
HTTPServer httpServer; |
||||||
|
|
||||||
|
WebServer::WebServer() |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
WebServer::~WebServer() |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
void WebServer::begin() |
||||||
|
{ |
||||||
|
::DebugLog.println("Creating server task... "); |
||||||
|
xTaskCreatePinnedToCore(WebServer::ServerTask_, "webserver", 6144, nullptr, 1, nullptr, WEBSERVER_RUNNING_CORE); |
||||||
|
} |
||||||
|
|
||||||
|
void WebServer::setStatus(const Status& status) |
||||||
|
{ |
||||||
|
status_ = status; |
||||||
|
} |
||||||
|
|
||||||
|
void WebServer::ServerTask_(void* params) |
||||||
|
{ |
||||||
|
// Main HTML page
|
||||||
|
ResourceNode* nodeRoot = new ResourceNode("/", "GET", &WebServer::HandleIndex_); |
||||||
|
ResourceNode* nodeIndex = new ResourceNode("/index.html", "GET", &WebServer::HandleIndex_); |
||||||
|
httpServer.registerNode(nodeRoot); |
||||||
|
httpServer.registerNode(nodeIndex); |
||||||
|
|
||||||
|
// API
|
||||||
|
ResourceNode* nodeGetStatus = new ResourceNode("/api/status", "GET", &WebServer::HandleGetStatus_); |
||||||
|
ResourceNode* nodePostInfo = new ResourceNode("/api/info", "POST", &WebServer::HandlePostInfo_); |
||||||
|
httpServer.registerNode(nodeGetStatus); |
||||||
|
httpServer.registerNode(nodePostInfo); |
||||||
|
|
||||||
|
// Default node (either a static file, if found, or a 404 error)
|
||||||
|
ResourceNode* nodeDefault = new ResourceNode("", "GET", &WebServer::HandleDefault_); |
||||||
|
httpServer.setDefaultNode(nodeDefault); |
||||||
|
|
||||||
|
::DebugLog.println("Starting server... "); |
||||||
|
httpServer.start(); |
||||||
|
if (httpServer.isRunning()) { |
||||||
|
::DebugLog.println("Server ready."); |
||||||
|
|
||||||
|
while(true) { |
||||||
|
httpServer.loop(); |
||||||
|
delay(1); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void WebServer::HandleIndex_(HTTPRequest * request, HTTPResponse * response) |
||||||
|
{ |
||||||
|
// Status code is 200 OK by default.
|
||||||
|
response->setHeader("Content-Type", "text/html"); |
||||||
|
|
||||||
|
File file = SPIFFS.open("/www/index.html"); |
||||||
|
SendContent_(file, response); |
||||||
|
file.close(); |
||||||
|
} |
||||||
|
|
||||||
|
void WebServer::HandleGetStatus_(httpsserver::HTTPRequest * request, httpsserver::HTTPResponse * response) |
||||||
|
{ |
||||||
|
const Status& status = ::WebServer.status_; |
||||||
|
|
||||||
|
int v = status.batteryVoltage; |
||||||
|
int c = status.batteryOutputCurrent; |
||||||
|
int s = (int)(status.speed * 1000.0f + 0.5f); |
||||||
|
int temp = status.temperature; |
||||||
|
int alt = status.altitude; |
||||||
|
|
||||||
|
int td = status.tripDistance; |
||||||
|
int ttt = status.tripTotalTime; |
||||||
|
int tmt = status.tripMovingTime; |
||||||
|
int tae = status.tripAscendingElevation / 100; // convert mm to dm
|
||||||
|
int tme = status.tripMotorEnergy / 360; // convert Joules to dWh (tenth of Wh)
|
||||||
|
|
||||||
|
float latitude = -1000.0f; |
||||||
|
float longitude = -1000.0f; |
||||||
|
char realtime[64] = {0}; |
||||||
|
|
||||||
|
const char* logFileName = DataLogger::get().currentLogFileName(); |
||||||
|
if(String(logFileName).startsWith("/log/")) logFileName += 5; |
||||||
|
|
||||||
|
int totalSize = (int)(SPIFFS.totalBytes() / 1000); |
||||||
|
int usedSize = (int)(SPIFFS.usedBytes() / 1000); |
||||||
|
|
||||||
|
char json[256]; |
||||||
|
sprintf(json, "{\"v\":%d,\"c\":%d,\"s\":%d,\"td\":%d,\"ttt\":%d,\"tmt\":%d,\"tae\":%d,\"tme\":%d,\"temp\":%d,\"alt\":%d,\"log\":\"%s\",\"tot\":%d,\"used\":%d,\"lat\":%.5f,\"lng\":%.5f,\"d\":\"%s\"}", v, c, s, td, ttt, tmt, tae, tme, temp, alt, logFileName, totalSize, usedSize, latitude, longitude, realtime); |
||||||
|
|
||||||
|
response->setHeader("Content-Type", "text/json"); |
||||||
|
response->print(json); |
||||||
|
} |
||||||
|
|
||||||
|
void WebServer::HandlePostInfo_(httpsserver::HTTPRequest * request, httpsserver::HTTPResponse * response) |
||||||
|
{ |
||||||
|
request->discardRequestBody(); |
||||||
|
} |
||||||
|
|
||||||
|
void WebServer::HandleDefault_(HTTPRequest * request, HTTPResponse * response) |
||||||
|
{ |
||||||
|
// Discard request body, if we received any
|
||||||
|
// We do this, as this is the default node and may also server POST/PUT requests
|
||||||
|
request->discardRequestBody(); |
||||||
|
|
||||||
|
std::string filePath = "/www"; |
||||||
|
std::string fullURL = request->getRequestString(); |
||||||
|
filePath = filePath + fullURL.substr(0, fullURL.find("?")); |
||||||
|
//::DebugLog.println(filePath.c_str());
|
||||||
|
File file = SPIFFS.open(filePath.c_str()); |
||||||
|
|
||||||
|
if(!file || file.isDirectory()) |
||||||
|
{ |
||||||
|
// We've not found any matching file, so this is a 404 error
|
||||||
|
|
||||||
|
response->setStatusCode(404); |
||||||
|
response->setStatusText("Not Found"); |
||||||
|
|
||||||
|
response->setHeader("Content-Type", "text/html"); |
||||||
|
|
||||||
|
response->println("<!DOCTYPE html>"); |
||||||
|
response->println("<html>"); |
||||||
|
response->println("<head><title>Not Found</title></head>"); |
||||||
|
response->println("<body><h1>404 Not Found</h1><p>The requested resource was not found on this server.</p></body>"); |
||||||
|
response->println("</html>"); |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
// We've found a file, send the content
|
||||||
|
|
||||||
|
response->setHeader("Cache-Control", "max-age=5184000"); |
||||||
|
|
||||||
|
SendContent_(file, response); |
||||||
|
} |
||||||
|
|
||||||
|
file.close(); |
||||||
|
} |
||||||
|
|
||||||
|
void WebServer::SendContent_(Stream& stream, httpsserver::HTTPResponse * response) |
||||||
|
{ |
||||||
|
uint8_t buffer[512]; |
||||||
|
while(stream.available()) |
||||||
|
{ |
||||||
|
size_t numBytes = stream.readBytes(buffer, sizeof(buffer)); |
||||||
|
response->write(buffer, numBytes); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,39 @@ |
|||||||
|
#pragma once |
||||||
|
|
||||||
|
#include "Status.h" |
||||||
|
|
||||||
|
class Stream; |
||||||
|
|
||||||
|
namespace httpsserver |
||||||
|
{ |
||||||
|
class HTTPRequest; |
||||||
|
class HTTPResponse; |
||||||
|
} |
||||||
|
|
||||||
|
namespace detail |
||||||
|
{ |
||||||
|
class WebServer |
||||||
|
{ |
||||||
|
public: |
||||||
|
WebServer(); |
||||||
|
~WebServer(); |
||||||
|
|
||||||
|
void begin(); |
||||||
|
|
||||||
|
void setStatus(const Status& status); |
||||||
|
|
||||||
|
private: |
||||||
|
static void ServerTask_(void* params); |
||||||
|
static void HandleIndex_(httpsserver::HTTPRequest * request, httpsserver::HTTPResponse * response); |
||||||
|
static void HandleGetStatus_(httpsserver::HTTPRequest * request, httpsserver::HTTPResponse * response); |
||||||
|
static void HandlePostInfo_(httpsserver::HTTPRequest * request, httpsserver::HTTPResponse * response); |
||||||
|
static void HandleDefault_(httpsserver::HTTPRequest * request, httpsserver::HTTPResponse * response); |
||||||
|
|
||||||
|
static void SendContent_(Stream& stream, httpsserver::HTTPResponse * response); |
||||||
|
|
||||||
|
private: |
||||||
|
Status status_; |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
extern detail::WebServer WebServer; |
Loading…
Reference in new issue