Merge branch 'main' into feature/egmp-soc-estimation

This commit is contained in:
Daniel Öster 2025-03-09 21:11:07 +02:00
commit 5800a3c281
67 changed files with 5808 additions and 7041 deletions

View file

@ -49,12 +49,13 @@
volatile unsigned long long bmsResetTimeOffset = 0;
// The current software version, shown on webserver
const char* version_number = "8.8.dev";
const char* version_number = "8.9.dev";
// Interval settings
uint16_t intervalUpdateValues = INTERVAL_1_S; // Interval at which to update inverter values / Modbus registers
unsigned long previousMillis10ms = 0;
unsigned long previousMillisUpdateVal = 0;
unsigned long lastMillisOverflowCheck = 0;
// Task time measurement for debugging and for setting CPU load events
int64_t core_task_time_us;
MyTimer core_task_timer_10s(INTERVAL_10_S);
@ -146,17 +147,8 @@ void setup() {
#endif
}
// Perform main program functions
void loop() {
START_TIME_MEASUREMENT(loop_func);
run_event_handling();
END_TIME_MEASUREMENT_MAX(loop_func, datalayer.system.status.loop_task_10s_max_us);
#ifdef FUNCTION_TIME_MEASUREMENT
if (loop_task_timer_10s.elapsed()) {
datalayer.system.status.loop_task_10s_max_us = 0;
}
#endif
}
// Loop empty, all functionality runs in tasks
void loop() {}
#if defined(LOG_CAN_TO_SD) || defined(LOG_TO_SD)
void logging_loop(void* task_time_us) {
@ -499,6 +491,11 @@ void update_calculated_values() {
datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery2.status.remaining_capacity_Wh;
}
#endif // DOUBLE_BATTERY
// Check if millis() has overflowed. Used in events to keep better track of time
if (millis() < lastMillisOverflowCheck) { // Overflow detected
datalayer.system.status.millisrolloverCount++;
}
lastMillisOverflowCheck = millis();
}
void update_values_inverter() {

View file

@ -1060,10 +1060,6 @@ void transmit_can_battery() {
case CELL_VOLTAGE_CELLNO:
current_cell_polled++;
if (current_cell_polled > 96) {
datalayer.battery.info.number_of_cells = 97;
#ifdef DOUBLE_BATTERY
datalayer.battery2.info.number_of_cells = 97;
#endif
cmdState = CELL_VOLTAGE_CELLNO_LAST;
} else {
cmdState = CELL_VOLTAGE_CELLNO;
@ -1150,13 +1146,14 @@ void setup_battery(void) { // Performs one time setup at startup
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_60AH;
datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
datalayer.system.status.battery_allows_contactor_closing = true;
datalayer.battery.info.number_of_cells = NUMBER_OF_CELLS;
#ifdef DOUBLE_BATTERY
datalayer.battery2.info.max_design_voltage_dV = datalayer.battery.info.max_design_voltage_dV;
datalayer.battery2.info.min_design_voltage_dV = datalayer.battery.info.min_design_voltage_dV;
datalayer.battery2.info.max_cell_voltage_deviation_mV = datalayer.battery.info.max_cell_voltage_deviation_mV;
datalayer.battery2.status.voltage_dV =
0; //Init voltage to 0 to allow contactor check to operate without fear of default values colliding
datalayer.battery2.info.number_of_cells = NUMBER_OF_CELLS;
#endif
pinMode(WUP_PIN1, OUTPUT);
digitalWrite(WUP_PIN1, HIGH); // Wake up the battery

View file

@ -18,6 +18,7 @@
#define MIN_PACK_VOLTAGE_94AH 2590 // Discharge stops if pack voltage exceeds this value
#define MAX_PACK_VOLTAGE_120AH 4030 // Charge stops if pack voltage exceeds this value
#define MIN_PACK_VOLTAGE_120AH 2680 // Discharge stops if pack voltage exceeds this value
#define NUMBER_OF_CELLS 96
void setup_battery(void);
void transmit_can_frame(CAN_frame* tx_frame, int interface);

View file

@ -33,7 +33,6 @@ static uint8_t battery_request_idx = 0;
static uint8_t rxConsecutiveFrames = 0;
static uint16_t min_max_voltage[2]; //contains cell min[0] and max[1] values in mV
static uint8_t cellcounter = 0;
static uint32_t remaining_capacity = 0;
static uint16_t cell_voltages[108]; //array with all the cellvoltages
static bool startedUp = false;
static uint8_t DTC_reset_counter = 0;
@ -134,12 +133,13 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer_extended.VolvoPolestar.UserRequestDTCreadout = false;
}
remaining_capacity = (78200 - CHARGE_ENERGY);
datalayer.battery.status.remaining_capacity_Wh = (datalayer.battery.info.total_capacity_Wh - CHARGE_ENERGY);
//datalayer.battery.status.real_soc = SOC_BMS; // Use BMS reported SOC, havent figured out how to get the BMS to calibrate empty/full yet
SOC_CALC = remaining_capacity / 78; // Use calculated SOC based on remaining_capacity
// Use calculated SOC based on remaining_capacity
SOC_CALC = (datalayer.battery.status.remaining_capacity_Wh / (datalayer.battery.info.total_capacity_Wh / 1000));
datalayer.battery.status.real_soc = SOC_CALC * 10;
datalayer.battery.status.real_soc = SOC_CALC * 10; //Add one decimal to make it pptt
if (BATT_U > MAX_U) // Protect if overcharged
{
@ -151,7 +151,6 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.status.voltage_dV = BATT_U * 10;
datalayer.battery.status.current_dA = BATT_I * 10;
datalayer.battery.status.remaining_capacity_Wh = remaining_capacity;
datalayer.battery.status.max_discharge_power_W = HvBattPwrLimDchaSlowAgi * 1000; //kW to W
datalayer.battery.status.max_charge_power_W = HvBattPwrLimChrgSlowAgi * 1000; //kW to W
@ -166,6 +165,22 @@ void update_values_battery() { //This function maps all the values fetched via
datalayer.battery.status.cell_voltages_mV[i] = cell_voltages[i];
}
//If we have enough cell values populated (atleast 96 read) AND number_of_cells not initialized yet
if (cell_voltages[95] > 0 && datalayer.battery.info.number_of_cells == 0) {
// We can determine whether we have 96S or 108S battery
if (datalayer.battery.status.cell_voltages_mV[107] > 0) {
datalayer.battery.info.number_of_cells = 108;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_108S_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_108S_DV;
datalayer.battery.info.total_capacity_Wh = 78200;
} else {
datalayer.battery.info.number_of_cells = 96;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_96S_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_96S_DV;
datalayer.battery.info.total_capacity_Wh = 69511;
}
}
#ifdef DEBUG_LOG
logging.print("BMS reported SOC%: ");
logging.println(SOC_BMS);
@ -463,11 +478,12 @@ void transmit_can_battery() {
}
void setup_battery(void) { // Performs one time setup at startup
strncpy(datalayer.system.info.battery_protocol, "Volvo / Polestar 78kWh battery", 63);
strncpy(datalayer.system.info.battery_protocol, "Volvo / Polestar 69/78kWh SPA battery", 63);
datalayer.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.number_of_cells = 108;
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
datalayer.battery.info.number_of_cells = 0; // Initializes when all cells have been read
datalayer.battery.info.total_capacity_Wh = 78200; //Startout in 78kWh mode (This value used for SOC calc)
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_108S_DV; //Startout with max allowed range
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_96S_DV; //Startout with min allowed range
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;

View file

@ -4,8 +4,10 @@
#include "../include.h"
#define BATTERY_SELECTED
#define MAX_PACK_VOLTAGE_DV 4540 //5000 = 500.0V
#define MIN_PACK_VOLTAGE_DV 2938
#define MAX_PACK_VOLTAGE_108S_DV 4540
#define MIN_PACK_VOLTAGE_108S_DV 2938
#define MAX_PACK_VOLTAGE_96S_DV 4030
#define MIN_PACK_VOLTAGE_96S_DV 2620
#define MAX_CELL_DEVIATION_MV 250
#define MAX_CELL_VOLTAGE_MV 4210 //Battery is put into emergency stop if one cell goes over this value
#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value

View file

@ -6,6 +6,7 @@
CAN_device_t CAN_cfg; // CAN Config
const int rx_queue_size = 10; // Receive Queue size
volatile bool send_ok_native = 0;
volatile bool send_ok_2515 = 0;
volatile bool send_ok_2518 = 0;
@ -146,7 +147,10 @@ void transmit_can_frame(CAN_frame* tx_frame, int interface) {
for (uint8_t i = 0; i < tx_frame->DLC; i++) {
frame.data.u8[i] = tx_frame->data.u8[i];
}
ESP32Can.CANWriteFrame(&frame);
send_ok_native = ESP32Can.CANWriteFrame(&frame);
if (!send_ok_native) {
datalayer.system.info.can_native_send_fail = true;
}
break;
case CAN_ADDON_MCP2515: {
#ifdef CAN_ADDON
@ -162,9 +166,7 @@ void transmit_can_frame(CAN_frame* tx_frame, int interface) {
send_ok_2515 = can.tryToSend(MCP2515Frame);
if (!send_ok_2515) {
set_event(EVENT_CAN_BUFFER_FULL, interface);
} else {
clear_event(EVENT_CAN_BUFFER_FULL);
datalayer.system.info.can_2515_send_fail = true;
}
#else // Interface not compiled, and settings try to use it
set_event(EVENT_INTERFACE_MISSING, interface);
@ -187,9 +189,7 @@ void transmit_can_frame(CAN_frame* tx_frame, int interface) {
}
send_ok_2518 = canfd.tryToSend(MCP2518Frame);
if (!send_ok_2518) {
set_event(EVENT_CANFD_BUFFER_FULL, interface);
} else {
clear_event(EVENT_CANFD_BUFFER_FULL);
datalayer.system.info.can_2518_send_fail = true;
}
#else // Interface not compiled, and settings try to use it
set_event(EVENT_INTERFACE_MISSING, interface);

View file

@ -228,10 +228,18 @@ typedef struct {
uint8_t can_replay_interface = CAN_NATIVE;
/** bool, determines if CAN replay should loop or not */
bool loop_playback = false;
/** bool, Native CAN failed to send flag */
bool can_native_send_fail = false;
/** bool, MCP2515 CAN failed to send flag */
bool can_2515_send_fail = false;
/** uint16_t, MCP2518 CANFD failed to send flag */
bool can_2518_send_fail = false;
} DATALAYER_SYSTEM_INFO_TYPE;
typedef struct {
/** Millis rollover count. Increments every 49.7 days. Used for keeping track on events */
uint8_t millisrolloverCount = 0;
#ifdef FUNCTION_TIME_MEASUREMENT
/** Core task measurement variable */
int64_t core_task_max_us = 0;
@ -241,8 +249,6 @@ typedef struct {
int64_t mqtt_task_10s_max_us = 0;
/** Wifi sub-task measurement variable, reset each 10 seconds */
int64_t wifi_task_10s_max_us = 0;
/** loop() task measurement variable, reset each 10 seconds */
int64_t loop_task_10s_max_us = 0;
/** OTA handling function measurement variable */
int64_t time_ota_us = 0;

View file

@ -19,6 +19,26 @@ battery_pause_status emulator_pause_status = NORMAL;
//battery pause status end
void update_machineryprotection() {
// Check health status of CAN interfaces
if (datalayer.system.info.can_native_send_fail) {
set_event(EVENT_CAN_NATIVE_TX_FAILURE, 0);
datalayer.system.info.can_native_send_fail = false;
} else {
clear_event(EVENT_CAN_NATIVE_TX_FAILURE);
}
if (datalayer.system.info.can_2515_send_fail) {
set_event(EVENT_CAN_BUFFER_FULL, 0);
datalayer.system.info.can_2515_send_fail = false;
} else {
clear_event(EVENT_CAN_BUFFER_FULL);
}
if (datalayer.system.info.can_2518_send_fail) {
set_event(EVENT_CANFD_BUFFER_FULL, 0);
datalayer.system.info.can_2518_send_fail = false;
} else {
clear_event(EVENT_CANFD_BUFFER_FULL);
}
// Start checking that the battery is within reason. Incase we see any funny business, raise an event!
// Pause function is on OR we have a critical fault event active

View file

@ -58,8 +58,6 @@ static EVENT_TYPE events;
static const char* EVENTS_ENUM_TYPE_STRING[] = {EVENTS_ENUM_TYPE(GENERATE_STRING)};
static const char* EVENTS_LEVEL_TYPE_STRING[] = {EVENTS_LEVEL_TYPE(GENERATE_STRING)};
static uint32_t lastMillis = millis();
/* Local function prototypes */
static void set_event(EVENTS_ENUM_TYPE event, uint8_t data, bool latched);
static void update_event_level(void);
@ -67,21 +65,6 @@ static void update_bms_status(void);
static void log_event(EVENTS_ENUM_TYPE event, uint8_t millisrolloverCount, uint32_t timestamp, uint8_t data);
static void print_event_log(void);
uint8_t millisrolloverCount = 0;
/* Exported functions */
/* Main execution function, should handle various continuous functionality */
void run_event_handling(void) {
uint32_t currentMillis = millis();
if (currentMillis < lastMillis) { // Overflow detected
millisrolloverCount++;
}
lastMillis = currentMillis;
update_event_level();
}
/* Initialization function */
void init_events(void) {
@ -136,7 +119,7 @@ void init_events(void) {
events.entries[EVENT_CAN_BUFFER_FULL].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_CAN_OVERRUN].level = EVENT_LEVEL_INFO;
events.entries[EVENT_CAN_CORRUPTED_WARNING].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_CAN_NATIVE_TX_FAILURE].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_CAN_NATIVE_TX_FAILURE].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_CAN_BATTERY_MISSING].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_CAN_BATTERY2_MISSING].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_CAN_CHARGER_MISSING].level = EVENT_LEVEL_INFO;
@ -275,9 +258,9 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) {
case EVENT_CANMCP2515_INIT_FAILURE:
return "CAN-MCP addon initialization failed. Check hardware";
case EVENT_CANFD_BUFFER_FULL:
return "MCP2518FD buffer overflowed. Some CAN messages were not sent. Contact developers.";
return "MCP2518FD message failed to send. Buffer full or no one on the bus to ACK the message!";
case EVENT_CAN_BUFFER_FULL:
return "MCP2515 buffer overflowed. Some CAN messages were not sent. Contact developers.";
return "MCP2515 message failed to send. Buffer full or no one on the bus to ACK the message!";
case EVENT_CAN_OVERRUN:
return "CAN message failed to send within defined time. Contact developers, CPU load might be too high.";
case EVENT_CAN_CORRUPTED_WARNING:
@ -510,7 +493,7 @@ static void set_event(EVENTS_ENUM_TYPE event, uint8_t data, bool latched) {
// We should set the event, update event info
events.entries[event].timestamp = millis();
events.entries[event].millisrolloverCount = millisrolloverCount;
events.entries[event].millisrolloverCount = datalayer.system.status.millisrolloverCount;
events.entries[event].data = data;
// Check if the event is latching
events.entries[event].state = latched ? EVENT_STATE_ACTIVE_LATCHED : EVENT_STATE_ACTIVE;
@ -583,8 +566,10 @@ static void log_event(EVENTS_ENUM_TYPE event, uint8_t millisrolloverCount, uint3
int entry_address = EE_EVENT_ENTRY_START_ADDRESS + EE_EVENT_ENTRY_SIZE * events.event_log_head_index;
// Prepare an event block to write
EVENT_LOG_ENTRY_TYPE entry = {
.event = event, .millisrolloverCount = millisrolloverCount, .timestamp = timestamp, .data = data};
EVENT_LOG_ENTRY_TYPE entry = {.event = event,
.millisrolloverCount = datalayer.system.status.millisrolloverCount,
.timestamp = timestamp,
.data = data};
// Put the event in (what I guess is) the RAM EEPROM mirror, or write buffer
EEPROM.put(entry_address, entry);

View file

@ -159,8 +159,6 @@ struct EventData {
const EVENTS_STRUCT_TYPE* event_pointer;
};
extern uint8_t millisrolloverCount; // number of times millis rollovers
const char* get_event_enum_string(EVENTS_ENUM_TYPE event);
const char* get_event_message_string(EVENTS_ENUM_TYPE event);
const char* get_event_level_string(EVENTS_ENUM_TYPE event);
@ -177,8 +175,6 @@ void set_event_MQTTpublished(EVENTS_ENUM_TYPE event);
const EVENTS_STRUCT_TYPE* get_event_pointer(EVENTS_ENUM_TYPE event);
void run_event_handling(void);
void run_sequence_on_target(void);
bool compareEventsByTimestampAsc(const EventData& a, const EventData& b);

View file

@ -1,4 +1,5 @@
#include "events_html.h"
#include "../../datalayer/datalayer.h"
const char EVENTS_HTML_START[] = R"=====(
<style>body{background-color:#000;color:#fff}.event-log{display:flex;flex-direction:column}.event{display:flex;flex-wrap:wrap;border:1px solid #fff;padding:10px}.event>div{flex:1;min-width:100px;max-width:90%;word-break:break-word}</style><div style="background-color:#303e47;padding:10px;margin-bottom:10px;border-radius:25px"><div class="event-log"><div class="event" style="background-color:#1e2c33;font-weight:700"><div>Event Type</div><div>Severity</div><div>Last Event</div><div>Count</div><div>Data</div><div>Message</div></div>
@ -48,7 +49,7 @@ String events_processor(const String& var) {
content.concat("<div class='event'>");
content.concat("<div>" + String(get_event_enum_string(event_handle)) + "</div>");
content.concat("<div>" + String(get_event_level_string(event_handle)) + "</div>");
content.concat("<div class='sec-ago'>" + String(millisrolloverCount) + ";" +
content.concat("<div class='sec-ago'>" + String(datalayer.system.status.millisrolloverCount) + ";" +
String(timestamp_now - event_pointer->timestamp) + "</div>");
content.concat("<div>" + String(event_pointer->occurences) + "</div>");
content.concat("<div>" + String(event_pointer->data) + "</div>");

View file

@ -940,8 +940,6 @@ String processor(const String& var) {
content +=
"<h4>WIFI function (MQTT task) max load last 10 s: " + String(datalayer.system.status.wifi_task_10s_max_us) +
" us</h4>";
content +=
"<h4>loop() task max load last 10 s: " + String(datalayer.system.status.loop_task_10s_max_us) + " us</h4>";
content += "<h4>Max load @ worst case execution of core task:</h4>";
content += "<h4>10ms function timing: " + String(datalayer.system.status.time_snap_10ms_us) + " us</h4>";
content += "<h4>Values function timing: " + String(datalayer.system.status.time_snap_values_us) + " us</h4>";

View file

@ -4,10 +4,10 @@
#include <Preferences.h>
#include <WiFi.h>
#include "../../include.h"
#include "../../lib/ESP32Async-AsyncTCP/src/AsyncTCP.h"
#include "../../lib/ESP32Async-ESPAsyncWebServer/src/ESPAsyncWebServer.h"
#include "../../lib/YiannisBourkelis-Uptime-Library/src/uptime_formatter.h"
#include "../../lib/ayushsharma82-ElegantOTA/src/ElegantOTA.h"
#include "../../lib/mathieucarbou-AsyncTCPSock/src/AsyncTCP.h"
extern const char* version_number; // The current software version, shown on webserver

View file

@ -1,15 +0,0 @@
set(COMPONENT_SRCDIRS
"src"
)
set(COMPONENT_ADD_INCLUDEDIRS
"src"
)
set(COMPONENT_REQUIRES
"arduino-esp32"
)
register_component()
target_compile_options(${COMPONENT_TARGET} PRIVATE -fno-rtti)

View file

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

View file

@ -1,55 +0,0 @@
![https://avatars.githubusercontent.com/u/195753706?s=96&v=4](https://avatars.githubusercontent.com/u/195753706?s=96&v=4)
# AsyncTCP
[![License: LGPL 3.0](https://img.shields.io/badge/License-LGPL%203.0-yellow.svg)](https://opensource.org/license/lgpl-3-0/)
[![Continuous Integration](https://github.com/ESP32Async/AsyncTCP/actions/workflows/ci.yml/badge.svg)](https://github.com/ESP32Async/AsyncTCP/actions/workflows/ci.yml)
[![PlatformIO Registry](https://badges.registry.platformio.org/packages/ESP32Async/library/AsyncTCP.svg)](https://registry.platformio.org/libraries/ESP32Async/AsyncTCP)
Discord Server: [https://discord.gg/X7zpGdyUcY](https://discord.gg/X7zpGdyUcY)
## Async TCP Library for ESP32 Arduino
This is a fully asynchronous TCP library, aimed at enabling trouble-free, multi-connection network environment for Espressif's ESP32 MCUs.
This library is the base for [ESPAsyncWebServer](https://github.com/ESP32Async/ESPAsyncWebServer)
## How to install
The library can be downloaded from the releases page at [https://github.com/ESP32Async/AsyncTCP/releases](https://github.com/ESP32Async/AsyncTCP/releases).
It is also deployed in these registries:
- Arduino Library Registry: [https://github.com/arduino/library-registry](https://github.com/arduino/library-registry)
- ESP Component Registry [https://components.espressif.com/components/esp32async/asynctcp/](https://components.espressif.com/components/esp32async/asynctcp/)
- PlatformIO Registry: [https://registry.platformio.org/libraries/esp32async/AsyncTCP](https://registry.platformio.org/libraries/esp32async/AsyncTCP)
- Use: `lib_deps=ESP32Async/AsyncTCP` to point to latest version
- Use: `lib_deps=ESP32Async/AsyncTCP @ ^<x.y.z>` to point to latest version with the same major version
- Use: `lib_deps=ESP32Async/AsyncTCP @ <x.y.z>` to always point to the same version (reproductible build)
## AsyncClient and AsyncServer
The base classes on which everything else is built. They expose all possible scenarios, but are really raw and require more skills to use.
## Important recommendations
Most of the crashes are caused by improper configuration of the library for the project.
Here are some recommendations to avoid them.
I personally use the following configuration in my projects:
```c++
-D CONFIG_ASYNC_TCP_MAX_ACK_TIME=5000 // (keep default)
-D CONFIG_ASYNC_TCP_PRIORITY=10 // (keep default)
-D CONFIG_ASYNC_TCP_QUEUE_SIZE=64 // (keep default)
-D CONFIG_ASYNC_TCP_RUNNING_CORE=1 // force async_tcp task to be on same core as the app (default is core 0)
-D CONFIG_ASYNC_TCP_STACK_SIZE=4096 // reduce the stack size (default is 16K)
```
## Compatibility
- ESP32
- Arduino Core 2.x and 3.x

View file

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

View file

@ -1,31 +0,0 @@
{
"name": "AsyncTCP",
"version": "3.3.5",
"description": "Asynchronous TCP Library for ESP32",
"keywords": "async,tcp",
"repository": {
"type": "git",
"url": "https://github.com/ESP32Async/AsyncTCP.git"
},
"authors":
{
"name": "ESP32Async",
"maintainer": true
},
"license": "LGPL-3.0",
"frameworks": "arduino",
"platforms": [
"espressif32",
"libretiny"
],
"export": {
"include": [
"examples",
"src",
"library.json",
"library.properties",
"LICENSE",
"README.md"
]
}
}

View file

@ -1,11 +0,0 @@
name=Async TCP
includes=AsyncTCP.h
version=3.3.5
author=ESP32Async
maintainer=ESP32Async
sentence=Async TCP Library for ESP32
paragraph=Async TCP Library for ESP32
category=Other
url=https://github.com/ESP32Async/AsyncTCP.git
architectures=*
license=LGPL-3.0

File diff suppressed because it is too large Load diff

View file

@ -1,332 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
#ifndef ASYNCTCP_H_
#define ASYNCTCP_H_
#include "AsyncTCPVersion.h"
#define ASYNCTCP_FORK_ESP32Async
#include "IPAddress.h"
#if ESP_IDF_VERSION_MAJOR < 5
#include "IPv6Address.h"
#endif
#include "lwip/ip6_addr.h"
#include "lwip/ip_addr.h"
#include <functional>
#ifndef LIBRETINY
#include "sdkconfig.h"
extern "C" {
#include "freertos/semphr.h"
#include "lwip/pbuf.h"
}
#else
extern "C" {
#include <lwip/pbuf.h>
#include <semphr.h>
}
#define CONFIG_ASYNC_TCP_RUNNING_CORE -1 // any available core
#endif
// If core is not defined, then we are running in Arduino or PIO
#ifndef CONFIG_ASYNC_TCP_RUNNING_CORE
#define CONFIG_ASYNC_TCP_RUNNING_CORE -1 // any available core
#endif
// guard AsyncTCP task with watchdog
#ifndef CONFIG_ASYNC_TCP_USE_WDT
#define CONFIG_ASYNC_TCP_USE_WDT 1
#endif
#ifndef CONFIG_ASYNC_TCP_STACK_SIZE
#define CONFIG_ASYNC_TCP_STACK_SIZE 8192 * 2
#endif
#ifndef CONFIG_ASYNC_TCP_PRIORITY
#define CONFIG_ASYNC_TCP_PRIORITY 10
#endif
#ifndef CONFIG_ASYNC_TCP_QUEUE_SIZE
#define CONFIG_ASYNC_TCP_QUEUE_SIZE 64
#endif
#ifndef CONFIG_ASYNC_TCP_MAX_ACK_TIME
#define CONFIG_ASYNC_TCP_MAX_ACK_TIME 5000
#endif
class AsyncClient;
#define ASYNC_WRITE_FLAG_COPY 0x01 // will allocate new buffer to hold the data while sending (else will hold reference to the data given)
#define ASYNC_WRITE_FLAG_MORE 0x02 // will not send PSH flag, meaning that there should be more data to be sent before the application should react.
typedef std::function<void(void *, AsyncClient *)> AcConnectHandler;
typedef std::function<void(void *, AsyncClient *, size_t len, uint32_t time)> AcAckHandler;
typedef std::function<void(void *, AsyncClient *, int8_t error)> AcErrorHandler;
typedef std::function<void(void *, AsyncClient *, void *data, size_t len)> AcDataHandler;
typedef std::function<void(void *, AsyncClient *, struct pbuf *pb)> AcPacketHandler;
typedef std::function<void(void *, AsyncClient *, uint32_t time)> AcTimeoutHandler;
struct tcp_pcb;
struct ip_addr;
class AsyncClient {
public:
AsyncClient(tcp_pcb *pcb = 0);
~AsyncClient();
AsyncClient &operator=(const AsyncClient &other);
AsyncClient &operator+=(const AsyncClient &other);
bool operator==(const AsyncClient &other);
bool operator!=(const AsyncClient &other) {
return !(*this == other);
}
bool connect(const IPAddress &ip, uint16_t port);
#if ESP_IDF_VERSION_MAJOR < 5
bool connect(const IPv6Address &ip, uint16_t port);
#endif
bool connect(const char *host, uint16_t port);
/**
* @brief close connection
*
* @param now - ignored
*/
void close(bool now = false);
// same as close()
void stop() {
close(false);
};
int8_t abort();
bool free();
// ack is not pending
bool canSend();
// TCP buffer space available
size_t space();
/**
* @brief add data to be send (but do not send yet)
* @note add() would call lwip's tcp_write()
By default apiflags=ASYNC_WRITE_FLAG_COPY
You could try to use apiflags with this flag unset to pass data by reference and avoid copy to socket buffer,
but looks like it does not work for Arduino's lwip in ESP32/IDF at least
it is enforced in https://github.com/espressif/esp-lwip/blob/0606eed9d8b98a797514fdf6eabb4daf1c8c8cd9/src/core/tcp_out.c#L422C5-L422C30
if LWIP_NETIF_TX_SINGLE_PBUF is set, and it is set indeed in IDF
https://github.com/espressif/esp-idf/blob/a0f798cfc4bbd624aab52b2c194d219e242d80c1/components/lwip/port/include/lwipopts.h#L744
*
* @param data
* @param size
* @param apiflags
* @return size_t amount of data that has been copied
*/
size_t add(const char *data, size_t size, uint8_t apiflags = ASYNC_WRITE_FLAG_COPY);
/**
* @brief send data previously add()'ed
*
* @return true on success
* @return false on error
*/
bool send();
/**
* @brief add and enqueue data for sending
* @note it is same as add() + send()
* @note only make sense when canSend() == true
*
* @param data
* @param size
* @param apiflags
* @return size_t
*/
size_t write(const char *data, size_t size, uint8_t apiflags = ASYNC_WRITE_FLAG_COPY);
/**
* @brief add and enqueue data for sending
* @note treats data as null-terminated string
*
* @param data
* @return size_t
*/
size_t write(const char *data) {
return data == NULL ? 0 : write(data, strlen(data));
};
uint8_t state();
bool connecting();
bool connected();
bool disconnecting();
bool disconnected();
// disconnected or disconnecting
bool freeable();
uint16_t getMss();
uint32_t getRxTimeout();
// no RX data timeout for the connection in seconds
void setRxTimeout(uint32_t timeout);
uint32_t getAckTimeout();
// no ACK timeout for the last sent packet in milliseconds
void setAckTimeout(uint32_t timeout);
void setNoDelay(bool nodelay);
bool getNoDelay();
void setKeepAlive(uint32_t ms, uint8_t cnt);
uint32_t getRemoteAddress();
uint16_t getRemotePort();
uint32_t getLocalAddress();
uint16_t getLocalPort();
#if LWIP_IPV6
ip6_addr_t getRemoteAddress6();
ip6_addr_t getLocalAddress6();
#if ESP_IDF_VERSION_MAJOR < 5
IPv6Address remoteIP6();
IPv6Address localIP6();
#else
IPAddress remoteIP6();
IPAddress localIP6();
#endif
#endif
// compatibility
IPAddress remoteIP();
uint16_t remotePort();
IPAddress localIP();
uint16_t localPort();
// set callback - on successful connect
void onConnect(AcConnectHandler cb, void *arg = 0);
// set callback - disconnected
void onDisconnect(AcConnectHandler cb, void *arg = 0);
// set callback - ack received
void onAck(AcAckHandler cb, void *arg = 0);
// set callback - unsuccessful connect or error
void onError(AcErrorHandler cb, void *arg = 0);
// set callback - data received (called if onPacket is not used)
void onData(AcDataHandler cb, void *arg = 0);
// set callback - data received
// !!! You MUST call ackPacket() or free the pbuf yourself to prevent memory leaks
void onPacket(AcPacketHandler cb, void *arg = 0);
// set callback - ack timeout
void onTimeout(AcTimeoutHandler cb, void *arg = 0);
// set callback - every 125ms when connected
void onPoll(AcConnectHandler cb, void *arg = 0);
// ack pbuf from onPacket
void ackPacket(struct pbuf *pb);
// ack data that you have not acked using the method below
size_t ack(size_t len);
// will not ack the current packet. Call from onData
void ackLater() {
_ack_pcb = false;
}
static const char *errorToString(int8_t error);
const char *stateToString();
// internal callbacks - Do NOT call any of the functions below in user code!
static int8_t _s_poll(void *arg, struct tcp_pcb *tpcb);
static int8_t _s_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *pb, int8_t err);
static int8_t _s_fin(void *arg, struct tcp_pcb *tpcb, int8_t err);
static int8_t _s_lwip_fin(void *arg, struct tcp_pcb *tpcb, int8_t err);
static void _s_error(void *arg, int8_t err);
static int8_t _s_sent(void *arg, struct tcp_pcb *tpcb, uint16_t len);
static int8_t _s_connected(void *arg, struct tcp_pcb *tpcb, int8_t err);
static void _s_dns_found(const char *name, struct ip_addr *ipaddr, void *arg);
int8_t _recv(tcp_pcb *pcb, pbuf *pb, int8_t err);
tcp_pcb *pcb() {
return _pcb;
}
protected:
bool _connect(ip_addr_t addr, uint16_t port);
tcp_pcb *_pcb;
int8_t _closed_slot;
AcConnectHandler _connect_cb;
void *_connect_cb_arg;
AcConnectHandler _discard_cb;
void *_discard_cb_arg;
AcAckHandler _sent_cb;
void *_sent_cb_arg;
AcErrorHandler _error_cb;
void *_error_cb_arg;
AcDataHandler _recv_cb;
void *_recv_cb_arg;
AcPacketHandler _pb_cb;
void *_pb_cb_arg;
AcTimeoutHandler _timeout_cb;
void *_timeout_cb_arg;
AcConnectHandler _poll_cb;
void *_poll_cb_arg;
bool _ack_pcb;
uint32_t _tx_last_packet;
uint32_t _rx_ack_len;
uint32_t _rx_last_packet;
uint32_t _rx_timeout;
uint32_t _rx_last_ack;
uint32_t _ack_timeout;
uint16_t _connect_port;
int8_t _close();
void _free_closed_slot();
bool _allocate_closed_slot();
int8_t _connected(tcp_pcb *pcb, int8_t err);
void _error(int8_t err);
int8_t _poll(tcp_pcb *pcb);
int8_t _sent(tcp_pcb *pcb, uint16_t len);
int8_t _fin(tcp_pcb *pcb, int8_t err);
int8_t _lwip_fin(tcp_pcb *pcb, int8_t err);
void _dns_found(struct ip_addr *ipaddr);
public:
AsyncClient *prev;
AsyncClient *next;
};
class AsyncServer {
public:
AsyncServer(IPAddress addr, uint16_t port);
#if ESP_IDF_VERSION_MAJOR < 5
AsyncServer(IPv6Address addr, uint16_t port);
#endif
AsyncServer(uint16_t port);
~AsyncServer();
void onClient(AcConnectHandler cb, void *arg);
void begin();
void end();
void setNoDelay(bool nodelay);
bool getNoDelay();
uint8_t status();
// Do not use any of the functions below!
static int8_t _s_accept(void *arg, tcp_pcb *newpcb, int8_t err);
static int8_t _s_accepted(void *arg, AsyncClient *client);
protected:
uint16_t _port;
bool _bind4 = false;
bool _bind6 = false;
IPAddress _addr;
#if ESP_IDF_VERSION_MAJOR < 5
IPv6Address _addr6;
#endif
bool _noDelay;
tcp_pcb *_pcb;
AcConnectHandler _connect_cb;
void *_connect_cb_arg;
int8_t _accept(tcp_pcb *newpcb, int8_t err);
int8_t _accepted(AsyncClient *client);
};
#endif /* ASYNCTCP_H_ */

View file

@ -1,40 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
/** Major version number (X.x.x) */
#define ASYNCTCP_VERSION_MAJOR 3
/** Minor version number (x.X.x) */
#define ASYNCTCP_VERSION_MINOR 3
/** Patch version number (x.x.X) */
#define ASYNCTCP_VERSION_PATCH 5
/**
* Macro to convert version number into an integer
*
* To be used in comparisons, such as ASYNCTCP_VERSION >= ASYNCTCP_VERSION_VAL(2, 0, 0)
*/
#define ASYNCTCP_VERSION_VAL(major, minor, patch) ((major << 16) | (minor << 8) | (patch))
/**
* Current version, as an integer
*
* To be used in comparisons, such as ASYNCTCP_VERSION_NUM >= ASYNCTCP_VERSION_VAL(2, 0, 0)
*/
#define ASYNCTCP_VERSION_NUM ASYNCTCP_VERSION_VAL(ASYNCTCP_VERSION_MAJOR, ASYNCTCP_VERSION_MINOR, ASYNCTCP_VERSION_PATCH)
/**
* Current version, as string
*/
#define df2xstr(s) #s
#define df2str(s) df2xstr(s)
#define ASYNCTCP_VERSION df2str(ASYNCTCP_VERSION_MAJOR) "." df2str(ASYNCTCP_VERSION_MINOR) "." df2str(ASYNCTCP_VERSION_PATCH)
#ifdef __cplusplus
}
#endif

View file

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

View file

@ -6,7 +6,7 @@
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
identity and expression, level of experience, education, socioeconomic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
{
"name": "ESPAsyncWebServer",
"version": "3.6.2",
"version": "3.7.2",
"description": "Asynchronous HTTP and WebSocket Server Library for ESP32, ESP8266 and RP2040. Supports: WebSocket, SSE, Authentication, Arduino Json 7, File Upload, Static File serving, URL Rewrite, URL Redirect, etc.",
"keywords": "http,async,websocket,webserver",
"homepage": "https://github.com/ESP32Async/ESPAsyncWebServer",
@ -24,7 +24,7 @@
{
"owner": "ESP32Async",
"name": "AsyncTCP",
"version": "^3.3.2",
"version": "^3.3.6",
"platforms": "espressif32"
},
{
@ -38,9 +38,9 @@
"platforms": "espressif8266"
},
{
"owner": "khoih-prog",
"name": "AsyncTCP_RP2040W",
"version": "^1.2.0",
"owner": "ayushsharma82",
"name": "RPAsyncTCP",
"version": "^1.3.1",
"platforms": "raspberrypi"
}
],

View file

@ -1,6 +1,6 @@
name=ESP Async WebServer
includes=ESPAsyncWebServer.h
version=3.6.2
version=3.7.2
author=ESP32Async
maintainer=ESP32Async
sentence=Asynchronous HTTP and WebSocket Server Library for ESP32, ESP8266 and RP2040
@ -8,4 +8,4 @@ paragraph=Supports: WebSocket, SSE, Authentication, Arduino Json 7, File Upload,
category=Other
url=https://github.com/ESP32Async/ESPAsyncWebServer
architectures=*
license=LGPL-3.0
license=LGPL-3.0

View file

@ -1,25 +1,9 @@
/*
Asynchronous WebServer library for Espressif MCUs
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
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"
#if defined(ESP32)
#include <rom/ets_sys.h>
#include <rom/ets_sys.h>
#endif
#include "AsyncEventSource.h"
@ -27,46 +11,54 @@
using namespace asyncsrv;
static String generateEventMessage(const char* message, const char* event, uint32_t id, uint32_t reconnect) {
static String generateEventMessage(const char *message, const char *event, uint32_t id, uint32_t reconnect) {
String str;
size_t len{0};
if (message)
if (message) {
len += strlen(message);
}
if (event)
if (event) {
len += strlen(event);
}
len += 42; // give it some overhead
len += 42; // give it some overhead
str.reserve(len);
if (!str.reserve(len)) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
return emptyString;
}
if (reconnect) {
str += T_retry_;
str += reconnect;
str += ASYNC_SSE_NEW_LINE_CHAR; // '\n'
str += ASYNC_SSE_NEW_LINE_CHAR; // '\n'
}
if (id) {
str += T_id__;
str += id;
str += ASYNC_SSE_NEW_LINE_CHAR; // '\n'
str += ASYNC_SSE_NEW_LINE_CHAR; // '\n'
}
if (event != NULL) {
str += T_event_;
str += event;
str += ASYNC_SSE_NEW_LINE_CHAR; // '\n'
str += ASYNC_SSE_NEW_LINE_CHAR; // '\n'
}
if (!message)
if (!message) {
return str;
}
size_t messageLen = strlen(message);
char* lineStart = (char*)message;
char* lineEnd;
char *lineStart = (char *)message;
char *lineEnd;
do {
char* nextN = strchr(lineStart, '\n');
char* nextR = strchr(lineStart, '\r');
char *nextN = strchr(lineStart, '\n');
char *nextR = strchr(lineStart, '\r');
if (nextN == NULL && nextR == NULL) {
// a message is a single-line string
str += T_data_;
@ -76,10 +68,10 @@ static String generateEventMessage(const char* message, const char* event, uint3
}
// a message is a multi-line string
char* nextLine = NULL;
if (nextN != NULL && nextR != NULL) { // windows line-ending \r\n
char *nextLine = NULL;
if (nextN != NULL && nextR != NULL) { // windows line-ending \r\n
if (nextR + 1 == nextN) {
// normal \r\n sequense
// normal \r\n sequence
lineEnd = nextR;
nextLine = nextN + 1;
} else {
@ -87,23 +79,23 @@ static String generateEventMessage(const char* message, const char* event, uint3
lineEnd = std::min(nextR, nextN);
nextLine = lineEnd + 1;
}
} else if (nextN != NULL) { // Unix/Mac OS X LF
} else if (nextN != NULL) { // Unix/Mac OS X LF
lineEnd = nextN;
nextLine = nextN + 1;
} else { // some ancient garbage
} else { // some ancient garbage
lineEnd = nextR;
nextLine = nextR + 1;
}
str += T_data_;
str.concat(lineStart, lineEnd - lineStart);
str += ASYNC_SSE_NEW_LINE_CHAR; // \n
str += ASYNC_SSE_NEW_LINE_CHAR; // \n
lineStart = nextLine;
} while (lineStart < ((char*)message + messageLen));
} while (lineStart < ((char *)message + messageLen));
// append another \n to terminate message
str += ASYNC_SSE_NEW_LINE_CHAR; // '\n'
str += ASYNC_SSE_NEW_LINE_CHAR; // '\n'
return str;
}
@ -123,9 +115,10 @@ size_t AsyncEventSourceMessage::ack(size_t len, __attribute__((unused)) uint32_t
return 0;
}
size_t AsyncEventSourceMessage::write(AsyncClient* client) {
if (!client)
size_t AsyncEventSourceMessage::write(AsyncClient *client) {
if (!client) {
return 0;
}
if (_sent >= _data->length() || !client->canSend()) {
return 0;
@ -143,31 +136,54 @@ size_t AsyncEventSourceMessage::write(AsyncClient* client) {
So let's just keep it enforced ASYNC_WRITE_FLAG_COPY and keep in mind that there is no zero-copy
*/
size_t written = client->add(_data->c_str() + _sent, len, ASYNC_WRITE_FLAG_COPY); // ASYNC_WRITE_FLAG_MORE
size_t written = client->add(_data->c_str() + _sent, len, ASYNC_WRITE_FLAG_COPY); // ASYNC_WRITE_FLAG_MORE
_sent += written;
return written;
}
size_t AsyncEventSourceMessage::send(AsyncClient* client) {
size_t AsyncEventSourceMessage::send(AsyncClient *client) {
size_t sent = write(client);
return sent && client->send() ? sent : 0;
}
// Client
AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest* request, AsyncEventSource* server)
: _client(request->client()), _server(server) {
AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server) : _client(request->client()), _server(server) {
if (request->hasHeader(T_Last_Event_ID))
if (request->hasHeader(T_Last_Event_ID)) {
_lastId = atoi(request->getHeader(T_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; static_cast<AsyncEventSourceClient*>(r)->_onAck(len, time); }, this);
_client->onPoll([](void* r, AsyncClient* c) { (void)c; static_cast<AsyncEventSourceClient*>(r)->_onPoll(); }, this);
_client->onAck(
[](void *r, AsyncClient *c, size_t len, uint32_t time) {
(void)c;
static_cast<AsyncEventSourceClient *>(r)->_onAck(len, time);
},
this
);
_client->onPoll(
[](void *r, AsyncClient *c) {
(void)c;
static_cast<AsyncEventSourceClient *>(r)->_onPoll();
},
this
);
_client->onData(NULL, NULL);
_client->onTimeout([this](void* r, AsyncClient* c __attribute__((unused)), uint32_t time) { static_cast<AsyncEventSourceClient*>(r)->_onTimeout(time); }, this);
_client->onDisconnect([this](void* r, AsyncClient* c) { static_cast<AsyncEventSourceClient*>(r)->_onDisconnect(); delete c; }, this);
_client->onTimeout(
[this](void *r, AsyncClient *c __attribute__((unused)), uint32_t time) {
static_cast<AsyncEventSourceClient *>(r)->_onTimeout(time);
},
this
);
_client->onDisconnect(
[this](void *r, AsyncClient *c) {
static_cast<AsyncEventSourceClient *>(r)->_onDisconnect();
delete c;
},
this
);
_server->_addClient(this);
delete request;
@ -183,7 +199,7 @@ AsyncEventSourceClient::~AsyncEventSourceClient() {
close();
}
bool AsyncEventSourceClient::_queueMessage(const char* message, size_t len) {
bool AsyncEventSourceClient::_queueMessage(const char *message, size_t len) {
if (_messageQueue.size() >= SSE_MAX_QUEUED_MESSAGES) {
#ifdef ESP8266
ets_printf(String(F("ERROR: Too many messages queued\n")).c_str());
@ -213,7 +229,7 @@ bool AsyncEventSourceClient::_queueMessage(const char* message, size_t len) {
return true;
}
bool AsyncEventSourceClient::_queueMessage(AsyncEvent_SharedData_t&& msg) {
bool AsyncEventSourceClient::_queueMessage(AsyncEvent_SharedData_t &&msg) {
if (_messageQueue.size() >= SSE_MAX_QUEUED_MESSAGES) {
#ifdef ESP8266
ets_printf(String(F("ERROR: Too many messages queued\n")).c_str());
@ -249,10 +265,11 @@ void AsyncEventSourceClient::_onAck(size_t len __attribute__((unused)), uint32_t
#endif
// adjust in-flight len
if (len < _inflight)
if (len < _inflight) {
_inflight -= len;
else
} else {
_inflight = 0;
}
// acknowledge as much messages's data as we got confirmed len from a AsyncTCP
while (len && _messageQueue.size()) {
@ -264,8 +281,9 @@ void AsyncEventSourceClient::_onAck(size_t len __attribute__((unused)), uint32_t
}
// try to send another batch of data
if (_messageQueue.size())
if (_messageQueue.size()) {
_runQueue();
}
}
void AsyncEventSourceClient::_onPoll() {
@ -279,31 +297,36 @@ void AsyncEventSourceClient::_onPoll() {
}
void AsyncEventSourceClient::_onTimeout(uint32_t time __attribute__((unused))) {
if (_client)
if (_client) {
_client->close(true);
}
}
void AsyncEventSourceClient::_onDisconnect() {
if (!_client)
if (!_client) {
return;
}
_client = nullptr;
_server->_handleDisconnect(this);
}
void AsyncEventSourceClient::close() {
if (_client)
if (_client) {
_client->close();
}
}
bool AsyncEventSourceClient::send(const char* message, const char* event, uint32_t id, uint32_t reconnect) {
if (!connected())
bool AsyncEventSourceClient::send(const char *message, const char *event, uint32_t id, uint32_t reconnect) {
if (!connected()) {
return false;
}
return _queueMessage(std::make_shared<String>(generateEventMessage(message, event, id, reconnect)));
}
void AsyncEventSourceClient::_runQueue() {
if (!_client)
if (!_client) {
return;
}
// there is no need to lock the mutex here, 'cause all the calls to this method must be already lock'ed
size_t total_bytes_written = 0;
@ -320,45 +343,52 @@ void AsyncEventSourceClient::_runQueue() {
}
// flush socket
if (total_bytes_written)
if (total_bytes_written) {
_client->send();
}
}
void AsyncEventSourceClient::set_max_inflight_bytes(size_t value) {
if (value >= SSE_MIN_INFLIGH && value <= SSE_MAX_INFLIGH)
if (value >= SSE_MIN_INFLIGH && value <= SSE_MAX_INFLIGH) {
_max_inflight = value;
}
}
/* AsyncEventSource */
void AsyncEventSource::authorizeConnect(ArAuthorizeConnectHandler cb) {
AsyncAuthorizationMiddleware* m = new AsyncAuthorizationMiddleware(401, cb);
AsyncAuthorizationMiddleware *m = new AsyncAuthorizationMiddleware(401, cb);
m->_freeOnRemoval = true;
addMiddleware(m);
}
void AsyncEventSource::_addClient(AsyncEventSourceClient* client) {
if (!client)
void AsyncEventSource::_addClient(AsyncEventSourceClient *client) {
if (!client) {
return;
}
#ifdef ESP32
std::lock_guard<std::mutex> lock(_client_queue_lock);
#endif
_clients.emplace_back(client);
if (_connectcb)
if (_connectcb) {
_connectcb(client);
}
_adjust_inflight_window();
}
void AsyncEventSource::_handleDisconnect(AsyncEventSourceClient* client) {
if (_disconnectcb)
void AsyncEventSource::_handleDisconnect(AsyncEventSourceClient *client) {
if (_disconnectcb) {
_disconnectcb(client);
}
#ifdef ESP32
std::lock_guard<std::mutex> lock(_client_queue_lock);
#endif
for (auto i = _clients.begin(); i != _clients.end(); ++i) {
if (i->get() == client)
if (i->get() == client) {
_clients.erase(i);
break;
}
}
_adjust_inflight_window();
}
@ -370,9 +400,10 @@ void AsyncEventSource::close() {
#ifdef ESP32
std::lock_guard<std::mutex> lock(_client_queue_lock);
#endif
for (const auto& c : _clients) {
if (c->connected())
for (const auto &c : _clients) {
if (c->connected()) {
c->close();
}
}
}
@ -383,31 +414,32 @@ size_t AsyncEventSource::avgPacketsWaiting() const {
#ifdef ESP32
std::lock_guard<std::mutex> lock(_client_queue_lock);
#endif
if (!_clients.size())
if (!_clients.size()) {
return 0;
}
for (const auto& c : _clients) {
for (const auto &c : _clients) {
if (c->connected()) {
aql += c->packetsWaiting();
++nConnectedClients;
}
}
return ((aql) + (nConnectedClients / 2)) / (nConnectedClients); // round up
return ((aql) + (nConnectedClients / 2)) / (nConnectedClients); // round up
}
AsyncEventSource::SendStatus AsyncEventSource::send(
const char* message, const char* event, uint32_t id, uint32_t reconnect) {
AsyncEventSource::SendStatus AsyncEventSource::send(const char *message, const char *event, uint32_t id, uint32_t reconnect) {
AsyncEvent_SharedData_t shared_msg = std::make_shared<String>(generateEventMessage(message, event, id, reconnect));
#ifdef ESP32
std::lock_guard<std::mutex> lock(_client_queue_lock);
#endif
size_t hits = 0;
size_t miss = 0;
for (const auto& c : _clients) {
if (c->write(shared_msg))
for (const auto &c : _clients) {
if (c->write(shared_msg)) {
++hits;
else
} else {
++miss;
}
}
return hits == 0 ? DISCARDED : (miss == 0 ? ENQUEUED : PARTIALLY_ENQUEUED);
}
@ -417,33 +449,36 @@ size_t AsyncEventSource::count() const {
std::lock_guard<std::mutex> lock(_client_queue_lock);
#endif
size_t n_clients{0};
for (const auto& i : _clients)
if (i->connected())
for (const auto &i : _clients) {
if (i->connected()) {
++n_clients;
}
}
return n_clients;
}
bool AsyncEventSource::canHandle(AsyncWebServerRequest* request) const {
bool AsyncEventSource::canHandle(AsyncWebServerRequest *request) const {
return request->isSSE() && request->url().equals(_url);
}
void AsyncEventSource::handleRequest(AsyncWebServerRequest* request) {
void AsyncEventSource::handleRequest(AsyncWebServerRequest *request) {
request->send(new AsyncEventSourceResponse(this));
}
void AsyncEventSource::_adjust_inflight_window() {
if (_clients.size()) {
size_t inflight = SSE_MAX_INFLIGH / _clients.size();
for (const auto& c : _clients)
for (const auto &c : _clients) {
c->set_max_inflight_bytes(inflight);
}
// Serial.printf("adjusted inflight to: %u\n", inflight);
}
}
/* Response */
AsyncEventSourceResponse::AsyncEventSourceResponse(AsyncEventSource* server) {
AsyncEventSourceResponse::AsyncEventSourceResponse(AsyncEventSource *server) {
_server = server;
_code = 200;
_contentType = T_text_event_stream;
@ -452,14 +487,14 @@ AsyncEventSourceResponse::AsyncEventSourceResponse(AsyncEventSource* server) {
addHeader(T_Connection, T_keep_alive);
}
void AsyncEventSourceResponse::_respond(AsyncWebServerRequest* request) {
void AsyncEventSourceResponse::_respond(AsyncWebServerRequest *request) {
String out;
_assembleHead(out, 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))) {
size_t AsyncEventSourceResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time __attribute__((unused))) {
if (len) {
new AsyncEventSourceClient(request, _server);
}

View file

@ -1,64 +1,48 @@
/*
Asynchronous WebServer library for Espressif MCUs
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
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 "../../ESP32Async-AsyncTCP/src/AsyncTCP.h"
#include <mutex>
#ifndef SSE_MAX_QUEUED_MESSAGES
#define SSE_MAX_QUEUED_MESSAGES 32
#endif
#define SSE_MIN_INFLIGH 2 * 1460 // allow 2 MSS packets
#define SSE_MAX_INFLIGH 16 * 1024 // but no more than 16k, no need to blow it, since same data is kept in local Q
#include "../../mathieucarbou-AsyncTCPSock/src/AsyncTCP.h"
#include <mutex>
#ifndef SSE_MAX_QUEUED_MESSAGES
#define SSE_MAX_QUEUED_MESSAGES 32
#endif
#define SSE_MIN_INFLIGH 2 * 1460 // allow 2 MSS packets
#define SSE_MAX_INFLIGH 16 * 1024 // but no more than 16k, no need to blow it, since same data is kept in local Q
#elif defined(ESP8266)
#include <ESPAsyncTCP.h>
#ifndef SSE_MAX_QUEUED_MESSAGES
#define SSE_MAX_QUEUED_MESSAGES 8
#endif
#define SSE_MIN_INFLIGH 2 * 1460 // allow 2 MSS packets
#define SSE_MAX_INFLIGH 8 * 1024 // but no more than 8k, no need to blow it, since same data is kept in local Q
#elif defined(TARGET_RP2040)
#include <AsyncTCP_RP2040W.h>
#ifndef SSE_MAX_QUEUED_MESSAGES
#define SSE_MAX_QUEUED_MESSAGES 32
#endif
#define SSE_MIN_INFLIGH 2 * 1460 // allow 2 MSS packets
#define SSE_MAX_INFLIGH 16 * 1024 // but no more than 16k, no need to blow it, since same data is kept in local Q
#include <ESPAsyncTCP.h>
#ifndef SSE_MAX_QUEUED_MESSAGES
#define SSE_MAX_QUEUED_MESSAGES 8
#endif
#define SSE_MIN_INFLIGH 2 * 1460 // allow 2 MSS packets
#define SSE_MAX_INFLIGH 8 * 1024 // but no more than 8k, no need to blow it, since same data is kept in local Q
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#ifndef SSE_MAX_QUEUED_MESSAGES
#define SSE_MAX_QUEUED_MESSAGES 32
#endif
#define SSE_MIN_INFLIGH 2 * 1460 // allow 2 MSS packets
#define SSE_MAX_INFLIGH 16 * 1024 // but no more than 16k, no need to blow it, since same data is kept in local Q
#endif
#include "ESPAsyncWebServer.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
#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
class AsyncEventSource;
class AsyncEventSourceResponse;
class AsyncEventSourceClient;
using ArEventHandlerFunction = std::function<void(AsyncEventSourceClient* client)>;
using ArEventHandlerFunction = std::function<void(AsyncEventSourceClient *client)>;
using ArAuthorizeConnectHandler = ArAuthorizeFunction;
// shared message object container
using AsyncEvent_SharedData_t = std::shared_ptr<String>;
@ -69,55 +53,67 @@ using AsyncEvent_SharedData_t = std::shared_ptr<String>;
*/
class AsyncEventSourceMessage {
private:
const AsyncEvent_SharedData_t _data;
size_t _sent{0}; // num of bytes already sent
size_t _acked{0}; // num of bytes acked
private:
const AsyncEvent_SharedData_t _data;
size_t _sent{0}; // num of bytes already sent
size_t _acked{0}; // num of bytes acked
public:
AsyncEventSourceMessage(AsyncEvent_SharedData_t data) : _data(data) {};
#ifdef ESP32
AsyncEventSourceMessage(const char* data, size_t len) : _data(std::make_shared<String>(data, len)) {};
public:
AsyncEventSourceMessage(AsyncEvent_SharedData_t data) : _data(data){};
#if defined(ESP32)
AsyncEventSourceMessage(const char *data, size_t len) : _data(std::make_shared<String>(data, len)){};
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
AsyncEventSourceMessage(const char *data, size_t len) : _data(std::make_shared<String>()) {
if (data && len > 0) {
_data->concat(data, len);
}
};
#else
// esp8266's String does not have constructor with data/length arguments. Use a concat method here
AsyncEventSourceMessage(const char* data, size_t len) { _data->concat(data, len); };
// esp8266's String does not have constructor with data/length arguments. Use a concat method here
AsyncEventSourceMessage(const char *data, size_t len) {
_data->concat(data, len);
};
#endif
/**
/**
* @brief acknowledge sending len bytes of data
* @note if num of bytes to ack is larger then the unacknowledged message length the number of carried over bytes are returned
*
* @param len bytes to acknowlegde
* @param len bytes to acknowledge
* @param time
* @return size_t number of extra bytes carried over
*/
size_t ack(size_t len, uint32_t time = 0);
size_t ack(size_t len, uint32_t time = 0);
/**
/**
* @brief write message data to client's buffer
* @note this method does NOT call client's send
*
* @param client
* @return size_t number of bytes written
*/
size_t write(AsyncClient* client);
size_t write(AsyncClient *client);
/**
/**
* @brief writes message data to client's buffer and calls client's send method
*
* @param client
* @return size_t returns num of bytes the clien was able to send()
*/
size_t send(AsyncClient* client);
size_t send(AsyncClient *client);
// returns true if full message's length were acked
bool finished() { return _acked == _data->length(); }
// returns true if full message's length were acked
bool finished() {
return _acked == _data->length();
}
/**
/**
* @brief returns true if all data has been sent already
*
*/
bool sent() { return _sent == _data->length(); }
bool sent() {
return _sent == _data->length();
}
};
/**
@ -125,25 +121,25 @@ class AsyncEventSourceMessage {
*
*/
class AsyncEventSourceClient {
private:
AsyncClient* _client;
AsyncEventSource* _server;
uint32_t _lastId{0};
size_t _inflight{0}; // num of unacknowledged bytes that has been written to socket buffer
size_t _max_inflight{SSE_MAX_INFLIGH}; // max num of unacknowledged bytes that could be written to socket buffer
std::list<AsyncEventSourceMessage> _messageQueue;
private:
AsyncClient *_client;
AsyncEventSource *_server;
uint32_t _lastId{0};
size_t _inflight{0}; // num of unacknowledged bytes that has been written to socket buffer
size_t _max_inflight{SSE_MAX_INFLIGH}; // max num of unacknowledged bytes that could be written to socket buffer
std::list<AsyncEventSourceMessage> _messageQueue;
#ifdef ESP32
mutable std::mutex _lockmq;
mutable std::mutex _lockmq;
#endif
bool _queueMessage(const char* message, size_t len);
bool _queueMessage(AsyncEvent_SharedData_t&& msg);
void _runQueue();
bool _queueMessage(const char *message, size_t len);
bool _queueMessage(AsyncEvent_SharedData_t &&msg);
void _runQueue();
public:
AsyncEventSourceClient(AsyncWebServerRequest* request, AsyncEventSource* server);
~AsyncEventSourceClient();
public:
AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server);
~AsyncEventSourceClient();
/**
/**
* @brief Send an SSE message to client
* it will craft an SSE message and place it to client's message queue
*
@ -154,11 +150,15 @@ class AsyncEventSourceClient {
* @return true if message was placed in a queue
* @return false if queue is full
*/
bool send(const char* message, const char* event = NULL, uint32_t id = 0, uint32_t reconnect = 0);
bool send(const String& message, const String& event, uint32_t id = 0, uint32_t reconnect = 0) { return send(message.c_str(), event.c_str(), id, reconnect); }
bool send(const String& message, const char* event, uint32_t id = 0, uint32_t reconnect = 0) { return send(message.c_str(), event, id, reconnect); }
bool send(const char *message, const char *event = NULL, uint32_t id = 0, uint32_t reconnect = 0);
bool send(const String &message, const String &event, uint32_t id = 0, uint32_t reconnect = 0) {
return send(message.c_str(), event.c_str(), id, reconnect);
}
bool send(const String &message, const char *event, uint32_t id = 0, uint32_t reconnect = 0) {
return send(message.c_str(), event, id, reconnect);
}
/**
/**
* @brief place supplied preformatted SSE message to the message queue
* @note message must a properly formatted SSE string according to https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events
*
@ -166,42 +166,56 @@ class AsyncEventSourceClient {
* @return true on success
* @return false on queue overflow or no client connected
*/
bool write(AsyncEvent_SharedData_t message) { return connected() && _queueMessage(std::move(message)); };
bool write(AsyncEvent_SharedData_t message) {
return connected() && _queueMessage(std::move(message));
};
[[deprecated("Use _write(AsyncEvent_SharedData_t message) instead to share same data with multiple SSE clients")]]
bool write(const char* message, size_t len) { return connected() && _queueMessage(message, len); };
[[deprecated("Use _write(AsyncEvent_SharedData_t message) instead to share same data with multiple SSE clients")]]
bool write(const char *message, size_t len) {
return connected() && _queueMessage(message, len);
};
// close client's connection
void close();
// close client's connection
void close();
// getters
// getters
AsyncClient* client() { return _client; }
bool connected() const { return _client && _client->connected(); }
uint32_t lastId() const { return _lastId; }
size_t packetsWaiting() const { return _messageQueue.size(); };
AsyncClient *client() {
return _client;
}
bool connected() const {
return _client && _client->connected();
}
uint32_t lastId() const {
return _lastId;
}
size_t packetsWaiting() const {
return _messageQueue.size();
};
/**
/**
* @brief Sets max amount of bytes that could be written to client's socket while awaiting delivery acknowledge
* used to throttle message delivery length to tradeoff memory consumption
* @note actual amount of data written could possible be a bit larger but no more than available socket buff space
*
* @param value
*/
void set_max_inflight_bytes(size_t value);
void set_max_inflight_bytes(size_t value);
/**
/**
* @brief Get current max inflight bytes value
*
* @return size_t
*/
size_t get_max_inflight_bytes() const { return _max_inflight; }
size_t get_max_inflight_bytes() const {
return _max_inflight;
}
// system callbacks (do not call if from user code!)
void _onAck(size_t len, uint32_t time);
void _onPoll();
void _onTimeout(uint32_t time);
void _onDisconnect();
// system callbacks (do not call if from user code!)
void _onAck(size_t len, uint32_t time);
void _onPoll();
void _onTimeout(uint32_t time);
void _onDisconnect();
};
/**
@ -210,44 +224,50 @@ class AsyncEventSourceClient {
*
*/
class AsyncEventSource : public AsyncWebHandler {
private:
String _url;
std::list<std::unique_ptr<AsyncEventSourceClient>> _clients;
private:
String _url;
std::list<std::unique_ptr<AsyncEventSourceClient>> _clients;
#ifdef ESP32
// Same as for individual messages, protect mutations of _clients list
// since simultaneous access from different tasks is possible
mutable std::mutex _client_queue_lock;
// Same as for individual messages, protect mutations of _clients list
// since simultaneous access from different tasks is possible
mutable std::mutex _client_queue_lock;
#endif
ArEventHandlerFunction _connectcb = nullptr;
ArEventHandlerFunction _disconnectcb = nullptr;
ArEventHandlerFunction _connectcb = nullptr;
ArEventHandlerFunction _disconnectcb = nullptr;
// this method manipulates in-fligh data size for connected client depending on number of active connections
void _adjust_inflight_window();
// this method manipulates in-fligh data size for connected client depending on number of active connections
void _adjust_inflight_window();
public:
typedef enum {
DISCARDED = 0,
ENQUEUED = 1,
PARTIALLY_ENQUEUED = 2,
} SendStatus;
public:
typedef enum {
DISCARDED = 0,
ENQUEUED = 1,
PARTIALLY_ENQUEUED = 2,
} SendStatus;
AsyncEventSource(const char* url) : _url(url) {};
AsyncEventSource(const String& url) : _url(url) {};
~AsyncEventSource() { close(); };
AsyncEventSource(const char *url) : _url(url){};
AsyncEventSource(const String &url) : _url(url){};
~AsyncEventSource() {
close();
};
const char* url() const { return _url.c_str(); }
// close all connected clients
void close();
const char *url() const {
return _url.c_str();
}
// close all connected clients
void close();
/**
/**
* @brief set on-connect callback for the client
* used to deliver messages to client on first connect
*
* @param cb
*/
void onConnect(ArEventHandlerFunction cb) { _connectcb = cb; }
void onConnect(ArEventHandlerFunction cb) {
_connectcb = cb;
}
/**
/**
* @brief Send an SSE message to client
* it will craft an SSE message and place it to all connected client's message queues
*
@ -257,36 +277,44 @@ class AsyncEventSource : public AsyncWebHandler {
* @param reconnect client's reconnect timeout
* @return SendStatus if message was placed in any/all/part of the client's queues
*/
SendStatus send(const char* message, const char* event = NULL, uint32_t id = 0, uint32_t reconnect = 0);
SendStatus send(const String& message, const String& event, uint32_t id = 0, uint32_t reconnect = 0) { return send(message.c_str(), event.c_str(), id, reconnect); }
SendStatus send(const String& message, const char* event, uint32_t id = 0, uint32_t reconnect = 0) { return send(message.c_str(), event, id, reconnect); }
SendStatus send(const char *message, const char *event = NULL, uint32_t id = 0, uint32_t reconnect = 0);
SendStatus send(const String &message, const String &event, uint32_t id = 0, uint32_t reconnect = 0) {
return send(message.c_str(), event.c_str(), id, reconnect);
}
SendStatus send(const String &message, const char *event, uint32_t id = 0, uint32_t reconnect = 0) {
return send(message.c_str(), event, id, reconnect);
}
// The client pointer sent to the callback is only for reference purposes. DO NOT CALL ANY METHOD ON IT !
void onDisconnect(ArEventHandlerFunction cb) { _disconnectcb = cb; }
void authorizeConnect(ArAuthorizeConnectHandler cb);
// The client pointer sent to the callback is only for reference purposes. DO NOT CALL ANY METHOD ON IT !
void onDisconnect(ArEventHandlerFunction cb) {
_disconnectcb = cb;
}
void authorizeConnect(ArAuthorizeConnectHandler cb);
// returns number of connected clients
size_t count() const;
// returns number of connected clients
size_t count() const;
// returns average number of messages pending in all client's queues
size_t avgPacketsWaiting() const;
// returns average number of messages pending in all client's queues
size_t avgPacketsWaiting() const;
// system callbacks (do not call from user code!)
void _addClient(AsyncEventSourceClient* client);
void _handleDisconnect(AsyncEventSourceClient* client);
bool canHandle(AsyncWebServerRequest* request) const override final;
void handleRequest(AsyncWebServerRequest* request) override final;
// system callbacks (do not call from user code!)
void _addClient(AsyncEventSourceClient *client);
void _handleDisconnect(AsyncEventSourceClient *client);
bool canHandle(AsyncWebServerRequest *request) const override final;
void handleRequest(AsyncWebServerRequest *request) override final;
};
class AsyncEventSourceResponse : public AsyncWebServerResponse {
private:
AsyncEventSource* _server;
private:
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; }
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_ */

View file

@ -1,108 +1,117 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
#include "AsyncJson.h"
#if ASYNC_JSON_SUPPORT == 1
#if ARDUINOJSON_VERSION_MAJOR == 5
#if ARDUINOJSON_VERSION_MAJOR == 5
AsyncJsonResponse::AsyncJsonResponse(bool isArray) : _isValid{false} {
_code = 200;
_contentType = asyncsrv::T_application_json;
if (isArray)
if (isArray) {
_root = _jsonBuffer.createArray();
else
} else {
_root = _jsonBuffer.createObject();
}
}
#elif ARDUINOJSON_VERSION_MAJOR == 6
#elif ARDUINOJSON_VERSION_MAJOR == 6
AsyncJsonResponse::AsyncJsonResponse(bool isArray, size_t maxJsonBufferSize) : _jsonBuffer(maxJsonBufferSize), _isValid{false} {
_code = 200;
_contentType = asyncsrv::T_application_json;
if (isArray)
if (isArray) {
_root = _jsonBuffer.createNestedArray();
else
} else {
_root = _jsonBuffer.createNestedObject();
}
}
#else
#else
AsyncJsonResponse::AsyncJsonResponse(bool isArray) : _isValid{false} {
_code = 200;
_contentType = asyncsrv::T_application_json;
if (isArray)
if (isArray) {
_root = _jsonBuffer.add<JsonArray>();
else
} else {
_root = _jsonBuffer.add<JsonObject>();
}
}
#endif
#endif
size_t AsyncJsonResponse::setLength() {
#if ARDUINOJSON_VERSION_MAJOR == 5
#if ARDUINOJSON_VERSION_MAJOR == 5
_contentLength = _root.measureLength();
#else
#else
_contentLength = measureJson(_root);
#endif
#endif
if (_contentLength) {
_isValid = true;
}
return _contentLength;
}
size_t AsyncJsonResponse::_fillBuffer(uint8_t* data, size_t len) {
size_t AsyncJsonResponse::_fillBuffer(uint8_t *data, size_t len) {
ChunkPrint dest(data, _sentLength, len);
#if ARDUINOJSON_VERSION_MAJOR == 5
#if ARDUINOJSON_VERSION_MAJOR == 5
_root.printTo(dest);
#else
#else
serializeJson(_root, dest);
#endif
#endif
return len;
}
#if ARDUINOJSON_VERSION_MAJOR == 6
#if ARDUINOJSON_VERSION_MAJOR == 6
PrettyAsyncJsonResponse::PrettyAsyncJsonResponse(bool isArray, size_t maxJsonBufferSize) : AsyncJsonResponse{isArray, maxJsonBufferSize} {}
#else
#else
PrettyAsyncJsonResponse::PrettyAsyncJsonResponse(bool isArray) : AsyncJsonResponse{isArray} {}
#endif
#endif
size_t PrettyAsyncJsonResponse::setLength() {
#if ARDUINOJSON_VERSION_MAJOR == 5
#if ARDUINOJSON_VERSION_MAJOR == 5
_contentLength = _root.measurePrettyLength();
#else
#else
_contentLength = measureJsonPretty(_root);
#endif
#endif
if (_contentLength) {
_isValid = true;
}
return _contentLength;
}
size_t PrettyAsyncJsonResponse::_fillBuffer(uint8_t* data, size_t len) {
size_t PrettyAsyncJsonResponse::_fillBuffer(uint8_t *data, size_t len) {
ChunkPrint dest(data, _sentLength, len);
#if ARDUINOJSON_VERSION_MAJOR == 5
#if ARDUINOJSON_VERSION_MAJOR == 5
_root.prettyPrintTo(dest);
#else
#else
serializeJsonPretty(_root, dest);
#endif
#endif
return len;
}
#if ARDUINOJSON_VERSION_MAJOR == 6
AsyncCallbackJsonWebHandler::AsyncCallbackJsonWebHandler(const String& uri, ArJsonRequestHandlerFunction onRequest, size_t maxJsonBufferSize)
: _uri(uri), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), maxJsonBufferSize(maxJsonBufferSize), _maxContentLength(16384) {}
#else
AsyncCallbackJsonWebHandler::AsyncCallbackJsonWebHandler(const String& uri, ArJsonRequestHandlerFunction onRequest)
: _uri(uri), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), _maxContentLength(16384) {}
#endif
#if ARDUINOJSON_VERSION_MAJOR == 6
AsyncCallbackJsonWebHandler::AsyncCallbackJsonWebHandler(const String &uri, ArJsonRequestHandlerFunction onRequest, size_t maxJsonBufferSize)
: _uri(uri), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), maxJsonBufferSize(maxJsonBufferSize), _maxContentLength(16384) {}
#else
AsyncCallbackJsonWebHandler::AsyncCallbackJsonWebHandler(const String &uri, ArJsonRequestHandlerFunction onRequest)
: _uri(uri), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), _maxContentLength(16384) {}
#endif
bool AsyncCallbackJsonWebHandler::canHandle(AsyncWebServerRequest* request) const {
if (!_onRequest || !request->isHTTP() || !(_method & request->method()))
bool AsyncCallbackJsonWebHandler::canHandle(AsyncWebServerRequest *request) const {
if (!_onRequest || !request->isHTTP() || !(_method & request->method())) {
return false;
}
if (_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri + "/")))
if (_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri + "/"))) {
return false;
}
if (request->method() != HTTP_GET && !request->contentType().equalsIgnoreCase(asyncsrv::T_application_json))
if (request->method() != HTTP_GET && !request->contentType().equalsIgnoreCase(asyncsrv::T_application_json)) {
return false;
}
return true;
}
void AsyncCallbackJsonWebHandler::handleRequest(AsyncWebServerRequest* request) {
void AsyncCallbackJsonWebHandler::handleRequest(AsyncWebServerRequest *request) {
if (_onRequest) {
if (request->method() == HTTP_GET) {
JsonVariant json;
@ -110,21 +119,21 @@ void AsyncCallbackJsonWebHandler::handleRequest(AsyncWebServerRequest* request)
return;
} else if (request->_tempObject != NULL) {
#if ARDUINOJSON_VERSION_MAJOR == 5
#if ARDUINOJSON_VERSION_MAJOR == 5
DynamicJsonBuffer jsonBuffer;
JsonVariant json = jsonBuffer.parse((uint8_t*)(request->_tempObject));
JsonVariant json = jsonBuffer.parse((uint8_t *)(request->_tempObject));
if (json.success()) {
#elif ARDUINOJSON_VERSION_MAJOR == 6
#elif ARDUINOJSON_VERSION_MAJOR == 6
DynamicJsonDocument jsonBuffer(this->maxJsonBufferSize);
DeserializationError error = deserializeJson(jsonBuffer, (uint8_t*)(request->_tempObject));
DeserializationError error = deserializeJson(jsonBuffer, (uint8_t *)(request->_tempObject));
if (!error) {
JsonVariant json = jsonBuffer.as<JsonVariant>();
#else
#else
JsonDocument jsonBuffer;
DeserializationError error = deserializeJson(jsonBuffer, (uint8_t*)(request->_tempObject));
DeserializationError error = deserializeJson(jsonBuffer, (uint8_t *)(request->_tempObject));
if (!error) {
JsonVariant json = jsonBuffer.as<JsonVariant>();
#endif
#endif
_onRequest(request, json);
return;
@ -136,16 +145,23 @@ void AsyncCallbackJsonWebHandler::handleRequest(AsyncWebServerRequest* request)
}
}
void AsyncCallbackJsonWebHandler::handleBody(AsyncWebServerRequest* request, uint8_t* data, size_t len, size_t index, size_t total) {
void AsyncCallbackJsonWebHandler::handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
if (_onRequest) {
_contentLength = total;
if (total > 0 && request->_tempObject == NULL && total < _maxContentLength) {
request->_tempObject = malloc(total);
if (request->_tempObject == NULL) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
request->abort();
return;
}
}
if (request->_tempObject != NULL) {
memcpy((uint8_t*)(request->_tempObject) + index, data, len);
memcpy((uint8_t *)(request->_tempObject) + index, data, len);
}
}
}
#endif // ASYNC_JSON_SUPPORT
#endif // ASYNC_JSON_SUPPORT

View file

@ -1,131 +1,119 @@
// AsyncJson.h
/*
Async Response to use with ArduinoJson and AsyncWebServer
Written by Andrew Melvin (SticilFace) with help from me-no-dev and BBlanchon.
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
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_
#if __has_include("ArduinoJson.h")
#include <ArduinoJson.h>
#if ARDUINOJSON_VERSION_MAJOR >= 5
#define ASYNC_JSON_SUPPORT 1
#else
#define ASYNC_JSON_SUPPORT 0
#endif // ARDUINOJSON_VERSION_MAJOR >= 5
#endif // __has_include("ArduinoJson.h")
#include <ArduinoJson.h>
#if ARDUINOJSON_VERSION_MAJOR >= 5
#define ASYNC_JSON_SUPPORT 1
#else
#define ASYNC_JSON_SUPPORT 0
#endif // ARDUINOJSON_VERSION_MAJOR >= 5
#endif // __has_include("ArduinoJson.h")
#if ASYNC_JSON_SUPPORT == 1
#include <ESPAsyncWebServer.h>
#include <ESPAsyncWebServer.h>
#include "ChunkPrint.h"
#include "ChunkPrint.h"
#if ARDUINOJSON_VERSION_MAJOR == 6
#ifndef DYNAMIC_JSON_DOCUMENT_SIZE
#define DYNAMIC_JSON_DOCUMENT_SIZE 1024
#endif
#endif
#if ARDUINOJSON_VERSION_MAJOR == 6
#ifndef DYNAMIC_JSON_DOCUMENT_SIZE
#define DYNAMIC_JSON_DOCUMENT_SIZE 1024
#endif
#endif
class AsyncJsonResponse : public AsyncAbstractResponse {
protected:
#if ARDUINOJSON_VERSION_MAJOR == 5
DynamicJsonBuffer _jsonBuffer;
#elif ARDUINOJSON_VERSION_MAJOR == 6
DynamicJsonDocument _jsonBuffer;
#else
JsonDocument _jsonBuffer;
#endif
protected:
#if ARDUINOJSON_VERSION_MAJOR == 5
DynamicJsonBuffer _jsonBuffer;
#elif ARDUINOJSON_VERSION_MAJOR == 6
DynamicJsonDocument _jsonBuffer;
#else
JsonDocument _jsonBuffer;
#endif
JsonVariant _root;
bool _isValid;
JsonVariant _root;
bool _isValid;
public:
#if ARDUINOJSON_VERSION_MAJOR == 6
AsyncJsonResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE);
#else
AsyncJsonResponse(bool isArray = false);
#endif
JsonVariant& getRoot() { return _root; }
bool _sourceValid() const { return _isValid; }
size_t setLength();
size_t getSize() const { return _jsonBuffer.size(); }
size_t _fillBuffer(uint8_t* data, size_t len);
#if ARDUINOJSON_VERSION_MAJOR >= 6
bool overflowed() const { return _jsonBuffer.overflowed(); }
#endif
public:
#if ARDUINOJSON_VERSION_MAJOR == 6
AsyncJsonResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE);
#else
AsyncJsonResponse(bool isArray = false);
#endif
JsonVariant &getRoot() {
return _root;
}
bool _sourceValid() const {
return _isValid;
}
size_t setLength();
size_t getSize() const {
return _jsonBuffer.size();
}
size_t _fillBuffer(uint8_t *data, size_t len);
#if ARDUINOJSON_VERSION_MAJOR >= 6
bool overflowed() const {
return _jsonBuffer.overflowed();
}
#endif
};
class PrettyAsyncJsonResponse : public AsyncJsonResponse {
public:
#if ARDUINOJSON_VERSION_MAJOR == 6
PrettyAsyncJsonResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE);
#else
PrettyAsyncJsonResponse(bool isArray = false);
#endif
size_t setLength();
size_t _fillBuffer(uint8_t* data, size_t len);
public:
#if ARDUINOJSON_VERSION_MAJOR == 6
PrettyAsyncJsonResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE);
#else
PrettyAsyncJsonResponse(bool isArray = false);
#endif
size_t setLength();
size_t _fillBuffer(uint8_t *data, size_t len);
};
typedef std::function<void(AsyncWebServerRequest* request, JsonVariant& json)> ArJsonRequestHandlerFunction;
typedef std::function<void(AsyncWebServerRequest *request, JsonVariant &json)> ArJsonRequestHandlerFunction;
class AsyncCallbackJsonWebHandler : public AsyncWebHandler {
protected:
String _uri;
WebRequestMethodComposite _method;
ArJsonRequestHandlerFunction _onRequest;
size_t _contentLength;
#if ARDUINOJSON_VERSION_MAJOR == 6
size_t maxJsonBufferSize;
#endif
size_t _maxContentLength;
protected:
String _uri;
WebRequestMethodComposite _method;
ArJsonRequestHandlerFunction _onRequest;
size_t _contentLength;
#if ARDUINOJSON_VERSION_MAJOR == 6
size_t maxJsonBufferSize;
#endif
size_t _maxContentLength;
public:
#if ARDUINOJSON_VERSION_MAJOR == 6
AsyncCallbackJsonWebHandler(const String& uri, ArJsonRequestHandlerFunction onRequest = nullptr, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE);
#else
AsyncCallbackJsonWebHandler(const String& uri, ArJsonRequestHandlerFunction onRequest = nullptr);
#endif
public:
#if ARDUINOJSON_VERSION_MAJOR == 6
AsyncCallbackJsonWebHandler(const String &uri, ArJsonRequestHandlerFunction onRequest = nullptr, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE);
#else
AsyncCallbackJsonWebHandler(const String &uri, ArJsonRequestHandlerFunction onRequest = nullptr);
#endif
void setMethod(WebRequestMethodComposite method) { _method = method; }
void setMaxContentLength(int maxContentLength) { _maxContentLength = maxContentLength; }
void onRequest(ArJsonRequestHandlerFunction fn) { _onRequest = fn; }
void setMethod(WebRequestMethodComposite method) {
_method = method;
}
void setMaxContentLength(int maxContentLength) {
_maxContentLength = maxContentLength;
}
void onRequest(ArJsonRequestHandlerFunction fn) {
_onRequest = fn;
}
bool canHandle(AsyncWebServerRequest* request) const override final;
void handleRequest(AsyncWebServerRequest* request) override final;
void handleUpload(__unused AsyncWebServerRequest* request, __unused const String& filename, __unused size_t index, __unused uint8_t* data, __unused size_t len, __unused bool final) override final {}
void handleBody(AsyncWebServerRequest* request, uint8_t* data, size_t len, size_t index, size_t total) override final;
bool isRequestHandlerTrivial() const override final { return !_onRequest; }
bool canHandle(AsyncWebServerRequest *request) const override final;
void handleRequest(AsyncWebServerRequest *request) override final;
void handleUpload(
__unused AsyncWebServerRequest *request, __unused const String &filename, __unused size_t index, __unused uint8_t *data, __unused size_t len,
__unused bool final
) override final {}
void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override final;
bool isRequestHandlerTrivial() const override final {
return !_onRequest;
}
};
#endif // ASYNC_JSON_SUPPORT == 1
#endif // ASYNC_JSON_SUPPORT == 1
#endif // ASYNC_JSON_H_
#endif // ASYNC_JSON_H_

View file

@ -1,26 +1,31 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
#include "AsyncMessagePack.h"
#if ASYNC_MSG_PACK_SUPPORT == 1
#if ARDUINOJSON_VERSION_MAJOR == 6
#if ARDUINOJSON_VERSION_MAJOR == 6
AsyncMessagePackResponse::AsyncMessagePackResponse(bool isArray, size_t maxJsonBufferSize) : _jsonBuffer(maxJsonBufferSize), _isValid{false} {
_code = 200;
_contentType = asyncsrv::T_application_msgpack;
if (isArray)
if (isArray) {
_root = _jsonBuffer.createNestedArray();
else
} else {
_root = _jsonBuffer.createNestedObject();
}
}
#else
#else
AsyncMessagePackResponse::AsyncMessagePackResponse(bool isArray) : _isValid{false} {
_code = 200;
_contentType = asyncsrv::T_application_msgpack;
if (isArray)
if (isArray) {
_root = _jsonBuffer.add<JsonArray>();
else
} else {
_root = _jsonBuffer.add<JsonObject>();
}
}
#endif
#endif
size_t AsyncMessagePackResponse::setLength() {
_contentLength = measureMsgPack(_root);
@ -30,34 +35,39 @@ size_t AsyncMessagePackResponse::setLength() {
return _contentLength;
}
size_t AsyncMessagePackResponse::_fillBuffer(uint8_t* data, size_t len) {
size_t AsyncMessagePackResponse::_fillBuffer(uint8_t *data, size_t len) {
ChunkPrint dest(data, _sentLength, len);
serializeMsgPack(_root, dest);
return len;
}
#if ARDUINOJSON_VERSION_MAJOR == 6
AsyncCallbackMessagePackWebHandler::AsyncCallbackMessagePackWebHandler(const String& uri, ArMessagePackRequestHandlerFunction onRequest, size_t maxJsonBufferSize)
: _uri(uri), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), maxJsonBufferSize(maxJsonBufferSize), _maxContentLength(16384) {}
#else
AsyncCallbackMessagePackWebHandler::AsyncCallbackMessagePackWebHandler(const String& uri, ArMessagePackRequestHandlerFunction onRequest)
: _uri(uri), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), _maxContentLength(16384) {}
#endif
#if ARDUINOJSON_VERSION_MAJOR == 6
AsyncCallbackMessagePackWebHandler::AsyncCallbackMessagePackWebHandler(
const String &uri, ArMessagePackRequestHandlerFunction onRequest, size_t maxJsonBufferSize
)
: _uri(uri), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), maxJsonBufferSize(maxJsonBufferSize), _maxContentLength(16384) {}
#else
AsyncCallbackMessagePackWebHandler::AsyncCallbackMessagePackWebHandler(const String &uri, ArMessagePackRequestHandlerFunction onRequest)
: _uri(uri), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), _maxContentLength(16384) {}
#endif
bool AsyncCallbackMessagePackWebHandler::canHandle(AsyncWebServerRequest* request) const {
if (!_onRequest || !request->isHTTP() || !(_method & request->method()))
bool AsyncCallbackMessagePackWebHandler::canHandle(AsyncWebServerRequest *request) const {
if (!_onRequest || !request->isHTTP() || !(_method & request->method())) {
return false;
}
if (_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri + "/")))
if (_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri + "/"))) {
return false;
}
if (request->method() != HTTP_GET && !request->contentType().equalsIgnoreCase(asyncsrv::T_application_msgpack))
if (request->method() != HTTP_GET && !request->contentType().equalsIgnoreCase(asyncsrv::T_application_msgpack)) {
return false;
}
return true;
}
void AsyncCallbackMessagePackWebHandler::handleRequest(AsyncWebServerRequest* request) {
void AsyncCallbackMessagePackWebHandler::handleRequest(AsyncWebServerRequest *request) {
if (_onRequest) {
if (request->method() == HTTP_GET) {
JsonVariant json;
@ -65,17 +75,17 @@ void AsyncCallbackMessagePackWebHandler::handleRequest(AsyncWebServerRequest* re
return;
} else if (request->_tempObject != NULL) {
#if ARDUINOJSON_VERSION_MAJOR == 6
#if ARDUINOJSON_VERSION_MAJOR == 6
DynamicJsonDocument jsonBuffer(this->maxJsonBufferSize);
DeserializationError error = deserializeMsgPack(jsonBuffer, (uint8_t*)(request->_tempObject));
DeserializationError error = deserializeMsgPack(jsonBuffer, (uint8_t *)(request->_tempObject));
if (!error) {
JsonVariant json = jsonBuffer.as<JsonVariant>();
#else
#else
JsonDocument jsonBuffer;
DeserializationError error = deserializeMsgPack(jsonBuffer, (uint8_t*)(request->_tempObject));
DeserializationError error = deserializeMsgPack(jsonBuffer, (uint8_t *)(request->_tempObject));
if (!error) {
JsonVariant json = jsonBuffer.as<JsonVariant>();
#endif
#endif
_onRequest(request, json);
return;
@ -87,16 +97,23 @@ void AsyncCallbackMessagePackWebHandler::handleRequest(AsyncWebServerRequest* re
}
}
void AsyncCallbackMessagePackWebHandler::handleBody(AsyncWebServerRequest* request, uint8_t* data, size_t len, size_t index, size_t total) {
void AsyncCallbackMessagePackWebHandler::handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
if (_onRequest) {
_contentLength = total;
if (total > 0 && request->_tempObject == NULL && total < _maxContentLength) {
request->_tempObject = malloc(total);
if (request->_tempObject == NULL) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
request->abort();
return;
}
}
if (request->_tempObject != NULL) {
memcpy((uint8_t*)(request->_tempObject) + index, data, len);
memcpy((uint8_t *)(request->_tempObject) + index, data, len);
}
}
}
#endif // ASYNC_MSG_PACK_SUPPORT
#endif // ASYNC_MSG_PACK_SUPPORT

View file

@ -1,3 +1,6 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
#pragma once
/*
@ -22,81 +25,102 @@
*/
#if __has_include("ArduinoJson.h")
#include <ArduinoJson.h>
#if ARDUINOJSON_VERSION_MAJOR >= 6
#define ASYNC_MSG_PACK_SUPPORT 1
#else
#define ASYNC_MSG_PACK_SUPPORT 0
#endif // ARDUINOJSON_VERSION_MAJOR >= 6
#endif // __has_include("ArduinoJson.h")
#include <ArduinoJson.h>
#if ARDUINOJSON_VERSION_MAJOR >= 6
#define ASYNC_MSG_PACK_SUPPORT 1
#else
#define ASYNC_MSG_PACK_SUPPORT 0
#endif // ARDUINOJSON_VERSION_MAJOR >= 6
#endif // __has_include("ArduinoJson.h")
#if ASYNC_MSG_PACK_SUPPORT == 1
#include <ESPAsyncWebServer.h>
#include <ESPAsyncWebServer.h>
#include "ChunkPrint.h"
#include "ChunkPrint.h"
#if ARDUINOJSON_VERSION_MAJOR == 6
#ifndef DYNAMIC_JSON_DOCUMENT_SIZE
#define DYNAMIC_JSON_DOCUMENT_SIZE 1024
#endif
#endif
#if ARDUINOJSON_VERSION_MAJOR == 6
#ifndef DYNAMIC_JSON_DOCUMENT_SIZE
#define DYNAMIC_JSON_DOCUMENT_SIZE 1024
#endif
#endif
class AsyncMessagePackResponse : public AsyncAbstractResponse {
protected:
#if ARDUINOJSON_VERSION_MAJOR == 6
DynamicJsonDocument _jsonBuffer;
#else
JsonDocument _jsonBuffer;
#endif
protected:
#if ARDUINOJSON_VERSION_MAJOR == 6
DynamicJsonDocument _jsonBuffer;
#else
JsonDocument _jsonBuffer;
#endif
JsonVariant _root;
bool _isValid;
JsonVariant _root;
bool _isValid;
public:
#if ARDUINOJSON_VERSION_MAJOR == 6
AsyncMessagePackResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE);
#else
AsyncMessagePackResponse(bool isArray = false);
#endif
JsonVariant& getRoot() { return _root; }
bool _sourceValid() const { return _isValid; }
size_t setLength();
size_t getSize() const { return _jsonBuffer.size(); }
size_t _fillBuffer(uint8_t* data, size_t len);
#if ARDUINOJSON_VERSION_MAJOR >= 6
bool overflowed() const { return _jsonBuffer.overflowed(); }
#endif
public:
#if ARDUINOJSON_VERSION_MAJOR == 6
AsyncMessagePackResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE);
#else
AsyncMessagePackResponse(bool isArray = false);
#endif
JsonVariant &getRoot() {
return _root;
}
bool _sourceValid() const {
return _isValid;
}
size_t setLength();
size_t getSize() const {
return _jsonBuffer.size();
}
size_t _fillBuffer(uint8_t *data, size_t len);
#if ARDUINOJSON_VERSION_MAJOR >= 6
bool overflowed() const {
return _jsonBuffer.overflowed();
}
#endif
};
typedef std::function<void(AsyncWebServerRequest* request, JsonVariant& json)> ArMessagePackRequestHandlerFunction;
typedef std::function<void(AsyncWebServerRequest *request, JsonVariant &json)> ArMessagePackRequestHandlerFunction;
class AsyncCallbackMessagePackWebHandler : public AsyncWebHandler {
protected:
String _uri;
WebRequestMethodComposite _method;
ArMessagePackRequestHandlerFunction _onRequest;
size_t _contentLength;
#if ARDUINOJSON_VERSION_MAJOR == 6
size_t maxJsonBufferSize;
#endif
size_t _maxContentLength;
protected:
String _uri;
WebRequestMethodComposite _method;
ArMessagePackRequestHandlerFunction _onRequest;
size_t _contentLength;
#if ARDUINOJSON_VERSION_MAJOR == 6
size_t maxJsonBufferSize;
#endif
size_t _maxContentLength;
public:
#if ARDUINOJSON_VERSION_MAJOR == 6
AsyncCallbackMessagePackWebHandler(const String& uri, ArMessagePackRequestHandlerFunction onRequest = nullptr, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE);
#else
AsyncCallbackMessagePackWebHandler(const String& uri, ArMessagePackRequestHandlerFunction onRequest = nullptr);
#endif
public:
#if ARDUINOJSON_VERSION_MAJOR == 6
AsyncCallbackMessagePackWebHandler(
const String &uri, ArMessagePackRequestHandlerFunction onRequest = nullptr, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE
);
#else
AsyncCallbackMessagePackWebHandler(const String &uri, ArMessagePackRequestHandlerFunction onRequest = nullptr);
#endif
void setMethod(WebRequestMethodComposite method) { _method = method; }
void setMaxContentLength(int maxContentLength) { _maxContentLength = maxContentLength; }
void onRequest(ArMessagePackRequestHandlerFunction fn) { _onRequest = fn; }
void setMethod(WebRequestMethodComposite method) {
_method = method;
}
void setMaxContentLength(int maxContentLength) {
_maxContentLength = maxContentLength;
}
void onRequest(ArMessagePackRequestHandlerFunction fn) {
_onRequest = fn;
}
bool canHandle(AsyncWebServerRequest* request) const override final;
void handleRequest(AsyncWebServerRequest* request) override final;
void handleUpload(__unused AsyncWebServerRequest* request, __unused const String& filename, __unused size_t index, __unused uint8_t* data, __unused size_t len, __unused bool final) override final {}
void handleBody(AsyncWebServerRequest* request, uint8_t* data, size_t len, size_t index, size_t total) override final;
bool isRequestHandlerTrivial() const override final { return !_onRequest; }
bool canHandle(AsyncWebServerRequest *request) const override final;
void handleRequest(AsyncWebServerRequest *request) override final;
void handleUpload(
__unused AsyncWebServerRequest *request, __unused const String &filename, __unused size_t index, __unused uint8_t *data, __unused size_t len,
__unused bool final
) override final {}
void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override final;
bool isRequestHandlerTrivial() const override final {
return !_onRequest;
}
};
#endif // ASYNC_MSG_PACK_SUPPORT == 1
#endif // ASYNC_MSG_PACK_SUPPORT == 1

View file

@ -1,22 +1,32 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
#include "ESPAsyncWebServer.h"
AsyncWebHeader::AsyncWebHeader(const String& data) {
if (!data)
AsyncWebHeader::AsyncWebHeader(const String &data) {
if (!data) {
return;
}
int index = data.indexOf(':');
if (index < 0)
if (index < 0) {
return;
}
_name = data.substring(0, index);
_value = data.substring(index + 2);
}
String AsyncWebHeader::toString() const {
String str;
str.reserve(_name.length() + _value.length() + 2);
str.concat(_name);
str.concat((char)0x3a);
str.concat((char)0x20);
str.concat(_value);
str.concat(asyncsrv::T_rn);
if (str.reserve(_name.length() + _value.length() + 2)) {
str.concat(_name);
str.concat((char)0x3a);
str.concat((char)0x20);
str.concat(_value);
str.concat(asyncsrv::T_rn);
} else {
#ifdef ESP32
log_e("Failed to allocate");
#endif
}
return str;
}

View file

@ -0,0 +1,40 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
/** Major version number (X.x.x) */
#define ASYNCWEBSERVER_VERSION_MAJOR 3
/** Minor version number (x.X.x) */
#define ASYNCWEBSERVER_VERSION_MINOR 7
/** Patch version number (x.x.X) */
#define ASYNCWEBSERVER_VERSION_PATCH 2
/**
* Macro to convert version number into an integer
*
* To be used in comparisons, such as ASYNCWEBSERVER_VERSION >= ASYNCWEBSERVER_VERSION_VAL(2, 0, 0)
*/
#define ASYNCWEBSERVER_VERSION_VAL(major, minor, patch) ((major << 16) | (minor << 8) | (patch))
/**
* Current version, as an integer
*
* To be used in comparisons, such as ASYNCWEBSERVER_VERSION_NUM >= ASYNCWEBSERVER_VERSION_VAL(2, 0, 0)
*/
#define ASYNCWEBSERVER_VERSION_NUM ASYNCWEBSERVER_VERSION_VAL(ASYNCWEBSERVER_VERSION_MAJOR, ASYNCWEBSERVER_VERSION_MINOR, ASYNCWEBSERVER_VERSION_PATCH)
/**
* Current version, as string
*/
#define df2xstr(s) #s
#define df2str(s) df2xstr(s)
#define ASYNCWEBSERVER_VERSION df2str(ASYNCWEBSERVER_VERSION_MAJOR) "." df2str(ASYNCWEBSERVER_VERSION_MINOR) "." df2str(ASYNCWEBSERVER_VERSION_PATCH)
#ifdef __cplusplus
}
#endif

View file

@ -1,43 +1,26 @@
/*
Asynchronous WebServer library for Espressif MCUs
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
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 "../../ESP32Async-AsyncTCP/src/AsyncTCP.h"
#include <mutex>
#ifndef WS_MAX_QUEUED_MESSAGES
#define WS_MAX_QUEUED_MESSAGES 32
#endif
#include "../../mathieucarbou-AsyncTCPSock/src/AsyncTCP.h"
#include <mutex>
#ifndef WS_MAX_QUEUED_MESSAGES
#define WS_MAX_QUEUED_MESSAGES 32
#endif
#elif defined(ESP8266)
#include <ESPAsyncTCP.h>
#ifndef WS_MAX_QUEUED_MESSAGES
#define WS_MAX_QUEUED_MESSAGES 8
#endif
#elif defined(TARGET_RP2040)
#include <AsyncTCP_RP2040W.h>
#ifndef WS_MAX_QUEUED_MESSAGES
#define WS_MAX_QUEUED_MESSAGES 32
#endif
#include <ESPAsyncTCP.h>
#ifndef WS_MAX_QUEUED_MESSAGES
#define WS_MAX_QUEUED_MESSAGES 8
#endif
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#ifndef WS_MAX_QUEUED_MESSAGES
#define WS_MAX_QUEUED_MESSAGES 32
#endif
#endif
#include "ESPAsyncWebServer.h"
@ -45,18 +28,18 @@
#include <memory>
#ifdef ESP8266
#include <Hash.h>
#ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library
#include <../src/Hash.h>
#endif
#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
#ifndef DEFAULT_MAX_WS_CLIENTS
#ifdef ESP32
#define DEFAULT_MAX_WS_CLIENTS 8
#else
#define DEFAULT_MAX_WS_CLIENTS 4
#endif
#ifdef ESP32
#define DEFAULT_MAX_WS_CLIENTS 8
#else
#define DEFAULT_MAX_WS_CLIENTS 4
#endif
#endif
using AsyncWebSocketSharedBuffer = std::shared_ptr<std::vector<uint8_t>>;
@ -67,311 +50,367 @@ class AsyncWebSocketClient;
class AsyncWebSocketControl;
typedef struct {
/** Message type as defined by enum AwsFrameType.
/** 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.
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.
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;
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_PING,
WS_EVT_PONG,
WS_EVT_ERROR,
WS_EVT_DATA } AwsEventType;
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_PING,
WS_EVT_PONG,
WS_EVT_ERROR,
WS_EVT_DATA
} AwsEventType;
class AsyncWebSocketMessageBuffer {
friend AsyncWebSocket;
friend AsyncWebSocketClient;
friend AsyncWebSocket;
friend AsyncWebSocketClient;
private:
AsyncWebSocketSharedBuffer _buffer;
private:
AsyncWebSocketSharedBuffer _buffer;
public:
AsyncWebSocketMessageBuffer() {}
explicit AsyncWebSocketMessageBuffer(size_t size);
AsyncWebSocketMessageBuffer(const uint8_t* data, size_t size);
//~AsyncWebSocketMessageBuffer();
bool reserve(size_t size);
uint8_t* get() { return _buffer->data(); }
size_t length() const { return _buffer->size(); }
public:
AsyncWebSocketMessageBuffer() {}
explicit AsyncWebSocketMessageBuffer(size_t size);
AsyncWebSocketMessageBuffer(const uint8_t *data, size_t size);
//~AsyncWebSocketMessageBuffer();
bool reserve(size_t size);
uint8_t *get() {
return _buffer->data();
}
size_t length() const {
return _buffer->size();
}
};
class AsyncWebSocketMessage {
private:
AsyncWebSocketSharedBuffer _WSbuffer;
uint8_t _opcode{WS_TEXT};
bool _mask{false};
AwsMessageStatus _status{WS_MSG_ERROR};
size_t _sent{};
size_t _ack{};
size_t _acked{};
private:
AsyncWebSocketSharedBuffer _WSbuffer;
uint8_t _opcode{WS_TEXT};
bool _mask{false};
AwsMessageStatus _status{WS_MSG_ERROR};
size_t _sent{};
size_t _ack{};
size_t _acked{};
public:
AsyncWebSocketMessage(AsyncWebSocketSharedBuffer buffer, uint8_t opcode = WS_TEXT, bool mask = false);
public:
AsyncWebSocketMessage(AsyncWebSocketSharedBuffer buffer, uint8_t opcode = WS_TEXT, bool mask = false);
bool finished() const { return _status != WS_MSG_SENDING; }
bool betweenFrames() const { return _acked == _ack; }
bool finished() const {
return _status != WS_MSG_SENDING;
}
bool betweenFrames() const {
return _acked == _ack;
}
void ack(size_t len, uint32_t time);
size_t send(AsyncClient* client);
void ack(size_t len, uint32_t time);
size_t send(AsyncClient *client);
};
class AsyncWebSocketClient {
private:
AsyncClient* _client;
AsyncWebSocket* _server;
uint32_t _clientId;
AwsClientStatus _status;
private:
AsyncClient *_client;
AsyncWebSocket *_server;
uint32_t _clientId;
AwsClientStatus _status;
#ifdef ESP32
mutable std::mutex _lock;
mutable std::mutex _lock;
#endif
std::deque<AsyncWebSocketControl> _controlQueue;
std::deque<AsyncWebSocketMessage> _messageQueue;
bool closeWhenFull = true;
std::deque<AsyncWebSocketControl> _controlQueue;
std::deque<AsyncWebSocketMessage> _messageQueue;
bool closeWhenFull = true;
uint8_t _pstate;
AwsFrameInfo _pinfo;
uint8_t _pstate;
AwsFrameInfo _pinfo;
uint32_t _lastMessageTime;
uint32_t _keepAlivePeriod;
uint32_t _lastMessageTime;
uint32_t _keepAlivePeriod;
bool _queueControl(uint8_t opcode, const uint8_t* data = NULL, size_t len = 0, bool mask = false);
bool _queueMessage(AsyncWebSocketSharedBuffer buffer, uint8_t opcode = WS_TEXT, bool mask = false);
void _runQueue();
void _clearQueue();
bool _queueControl(uint8_t opcode, const uint8_t *data = NULL, size_t len = 0, bool mask = false);
bool _queueMessage(AsyncWebSocketSharedBuffer buffer, uint8_t opcode = WS_TEXT, bool mask = false);
void _runQueue();
void _clearQueue();
public:
void* _tempObject;
public:
void *_tempObject;
AsyncWebSocketClient(AsyncWebServerRequest* request, AsyncWebSocket* server);
~AsyncWebSocketClient();
AsyncWebSocketClient(AsyncWebServerRequest *request, AsyncWebSocket *server);
~AsyncWebSocketClient();
// client id increments for the given server
uint32_t id() const { return _clientId; }
AwsClientStatus status() const { return _status; }
AsyncClient* client() { return _client; }
const AsyncClient* client() const { return _client; }
AsyncWebSocket* server() { return _server; }
const AsyncWebSocket* server() const { return _server; }
AwsFrameInfo const& pinfo() const { return _pinfo; }
// client id increments for the given server
uint32_t id() const {
return _clientId;
}
AwsClientStatus status() const {
return _status;
}
AsyncClient *client() {
return _client;
}
const AsyncClient *client() const {
return _client;
}
AsyncWebSocket *server() {
return _server;
}
const AsyncWebSocket *server() const {
return _server;
}
AwsFrameInfo const &pinfo() const {
return _pinfo;
}
// - If "true" (default), the connection will be closed if the message queue is full.
// This is the default behavior in yubox-node-org, which is not silently discarding messages but instead closes the connection.
// The big issue with this behavior is that is can cause the UI to automatically re-create a new WS connection, which can be filled again,
// and so on, causing a resource exhaustion.
//
// - If "false", the incoming message will be discarded if the queue is full.
// This is the default behavior in the original ESPAsyncWebServer library from me-no-dev.
// This behavior allows the best performance at the expense of unreliable message delivery in case the queue is full (some messages may be lost).
//
// - In any case, when the queue is full, a message is logged.
// - IT is recommended to use the methods queueIsFull(), availableForWriteAll(), availableForWrite(clientId) to check if the queue is full before sending a message.
//
// Usage:
// - can be set in the onEvent listener when connecting (event type is: WS_EVT_CONNECT)
//
// Use cases:,
// - if using websocket to send logging messages, maybe some loss is acceptable.
// - But if using websocket to send UI update messages, maybe the connection should be closed and the UI redrawn.
void setCloseClientOnQueueFull(bool close) { closeWhenFull = close; }
bool willCloseClientOnQueueFull() const { return closeWhenFull; }
// - If "true" (default), the connection will be closed if the message queue is full.
// This is the default behavior in yubox-node-org, which is not silently discarding messages but instead closes the connection.
// The big issue with this behavior is that is can cause the UI to automatically re-create a new WS connection, which can be filled again,
// and so on, causing a resource exhaustion.
//
// - If "false", the incoming message will be discarded if the queue is full.
// This is the default behavior in the original ESPAsyncWebServer library from me-no-dev.
// This behavior allows the best performance at the expense of unreliable message delivery in case the queue is full (some messages may be lost).
//
// - In any case, when the queue is full, a message is logged.
// - IT is recommended to use the methods queueIsFull(), availableForWriteAll(), availableForWrite(clientId) to check if the queue is full before sending a message.
//
// Usage:
// - can be set in the onEvent listener when connecting (event type is: WS_EVT_CONNECT)
//
// Use cases:,
// - if using websocket to send logging messages, maybe some loss is acceptable.
// - But if using websocket to send UI update messages, maybe the connection should be closed and the UI redrawn.
void setCloseClientOnQueueFull(bool close) {
closeWhenFull = close;
}
bool willCloseClientOnQueueFull() const {
return closeWhenFull;
}
IPAddress remoteIP() const;
uint16_t remotePort() const;
IPAddress remoteIP() const;
uint16_t remotePort() const;
bool shouldBeDeleted() const { return !_client; }
bool shouldBeDeleted() const {
return !_client;
}
// control frames
void close(uint16_t code = 0, const char* message = NULL);
bool ping(const uint8_t* data = NULL, size_t len = 0);
// control frames
void close(uint16_t code = 0, const char *message = NULL);
bool ping(const uint8_t *data = NULL, size_t len = 0);
// set auto-ping period in seconds. disabled if zero (default)
void keepAlivePeriod(uint16_t seconds) {
_keepAlivePeriod = seconds * 1000;
}
uint16_t keepAlivePeriod() {
return (uint16_t)(_keepAlivePeriod / 1000);
}
// 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(AsyncWebSocketSharedBuffer buffer, uint8_t opcode = WS_TEXT, bool mask = false) { _queueMessage(buffer, opcode, mask); }
bool queueIsFull() const;
size_t queueLen() const;
// data packets
void message(AsyncWebSocketSharedBuffer buffer, uint8_t opcode = WS_TEXT, bool mask = false) {
_queueMessage(buffer, opcode, mask);
}
bool queueIsFull() const;
size_t queueLen() const;
size_t printf(const char* format, ...) __attribute__((format(printf, 2, 3)));
size_t printf(const char *format, ...) __attribute__((format(printf, 2, 3)));
bool text(AsyncWebSocketSharedBuffer buffer);
bool text(const uint8_t* message, size_t len);
bool text(const char* message, size_t len);
bool text(const char* message);
bool text(const String& message);
bool text(AsyncWebSocketMessageBuffer* buffer);
bool text(AsyncWebSocketSharedBuffer buffer);
bool text(const uint8_t *message, size_t len);
bool text(const char *message, size_t len);
bool text(const char *message);
bool text(const String &message);
bool text(AsyncWebSocketMessageBuffer *buffer);
bool binary(AsyncWebSocketSharedBuffer buffer);
bool binary(const uint8_t* message, size_t len);
bool binary(const char* message, size_t len);
bool binary(const char* message);
bool binary(const String& message);
bool binary(AsyncWebSocketMessageBuffer* buffer);
bool binary(AsyncWebSocketSharedBuffer buffer);
bool binary(const uint8_t *message, size_t len);
bool binary(const char *message, size_t len);
bool binary(const char *message);
bool binary(const String &message);
bool binary(AsyncWebSocketMessageBuffer *buffer);
bool canSend() const;
bool canSend() const;
// system callbacks (do not call)
void _onAck(size_t len, uint32_t time);
void _onError(int8_t);
void _onPoll();
void _onTimeout(uint32_t time);
void _onDisconnect();
void _onData(void* pbuf, size_t plen);
// 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);
#ifdef ESP8266
size_t printf_P(PGM_P formatP, ...) __attribute__((format(printf, 2, 3)));
bool text(const __FlashStringHelper* message);
bool binary(const __FlashStringHelper* message, size_t len);
size_t printf_P(PGM_P formatP, ...) __attribute__((format(printf, 2, 3)));
bool text(const __FlashStringHelper *message);
bool binary(const __FlashStringHelper *message, size_t len);
#endif
};
using AwsHandshakeHandler = std::function<bool(AsyncWebServerRequest* request)>;
using AwsEventHandler = std::function<void(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len)>;
using AwsHandshakeHandler = std::function<bool(AsyncWebServerRequest *request)>;
using AwsEventHandler = std::function<void(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len)>;
// WebServer Handler implementation that plays the role of a socket server
class AsyncWebSocket : public AsyncWebHandler {
private:
String _url;
std::list<AsyncWebSocketClient> _clients;
uint32_t _cNextId;
AwsEventHandler _eventHandler{nullptr};
AwsHandshakeHandler _handshakeHandler;
bool _enabled;
private:
String _url;
std::list<AsyncWebSocketClient> _clients;
uint32_t _cNextId;
AwsEventHandler _eventHandler{nullptr};
AwsHandshakeHandler _handshakeHandler;
bool _enabled;
#ifdef ESP32
mutable std::mutex _lock;
mutable std::mutex _lock;
#endif
public:
typedef enum {
DISCARDED = 0,
ENQUEUED = 1,
PARTIALLY_ENQUEUED = 2,
} SendStatus;
public:
typedef enum {
DISCARDED = 0,
ENQUEUED = 1,
PARTIALLY_ENQUEUED = 2,
} SendStatus;
explicit AsyncWebSocket(const char* url) : _url(url), _cNextId(1), _enabled(true) {}
AsyncWebSocket(const String& url) : _url(url), _cNextId(1), _enabled(true) {}
~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);
explicit AsyncWebSocket(const char *url) : _url(url), _cNextId(1), _enabled(true) {}
AsyncWebSocket(const String &url) : _url(url), _cNextId(1), _enabled(true) {}
~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) != nullptr; }
size_t count() const;
AsyncWebSocketClient *client(uint32_t id);
bool hasClient(uint32_t id) {
return client(id) != nullptr;
}
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 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);
bool ping(uint32_t id, const uint8_t* data = NULL, size_t len = 0);
SendStatus pingAll(const uint8_t* data = NULL, size_t len = 0); // done
bool ping(uint32_t id, const uint8_t *data = NULL, size_t len = 0);
SendStatus pingAll(const uint8_t *data = NULL, size_t len = 0); // done
bool text(uint32_t id, const uint8_t* message, size_t len);
bool text(uint32_t id, const char* message, size_t len);
bool text(uint32_t id, const char* message);
bool text(uint32_t id, const String& message);
bool text(uint32_t id, AsyncWebSocketMessageBuffer* buffer);
bool text(uint32_t id, AsyncWebSocketSharedBuffer buffer);
bool text(uint32_t id, const uint8_t *message, size_t len);
bool text(uint32_t id, const char *message, size_t len);
bool text(uint32_t id, const char *message);
bool text(uint32_t id, const String &message);
bool text(uint32_t id, AsyncWebSocketMessageBuffer *buffer);
bool text(uint32_t id, AsyncWebSocketSharedBuffer buffer);
SendStatus textAll(const uint8_t* message, size_t len);
SendStatus textAll(const char* message, size_t len);
SendStatus textAll(const char* message);
SendStatus textAll(const String& message);
SendStatus textAll(AsyncWebSocketMessageBuffer* buffer);
SendStatus textAll(AsyncWebSocketSharedBuffer buffer);
SendStatus textAll(const uint8_t *message, size_t len);
SendStatus textAll(const char *message, size_t len);
SendStatus textAll(const char *message);
SendStatus textAll(const String &message);
SendStatus textAll(AsyncWebSocketMessageBuffer *buffer);
SendStatus textAll(AsyncWebSocketSharedBuffer buffer);
bool binary(uint32_t id, const uint8_t* message, size_t len);
bool binary(uint32_t id, const char* message, size_t len);
bool binary(uint32_t id, const char* message);
bool binary(uint32_t id, const String& message);
bool binary(uint32_t id, AsyncWebSocketMessageBuffer* buffer);
bool binary(uint32_t id, AsyncWebSocketSharedBuffer buffer);
bool binary(uint32_t id, const uint8_t *message, size_t len);
bool binary(uint32_t id, const char *message, size_t len);
bool binary(uint32_t id, const char *message);
bool binary(uint32_t id, const String &message);
bool binary(uint32_t id, AsyncWebSocketMessageBuffer *buffer);
bool binary(uint32_t id, AsyncWebSocketSharedBuffer buffer);
SendStatus binaryAll(const uint8_t* message, size_t len);
SendStatus binaryAll(const char* message, size_t len);
SendStatus binaryAll(const char* message);
SendStatus binaryAll(const String& message);
SendStatus binaryAll(AsyncWebSocketMessageBuffer* buffer);
SendStatus binaryAll(AsyncWebSocketSharedBuffer buffer);
SendStatus binaryAll(const uint8_t *message, size_t len);
SendStatus binaryAll(const char *message, size_t len);
SendStatus binaryAll(const char *message);
SendStatus binaryAll(const String &message);
SendStatus binaryAll(AsyncWebSocketMessageBuffer *buffer);
SendStatus binaryAll(AsyncWebSocketSharedBuffer buffer);
size_t printf(uint32_t id, const char* format, ...) __attribute__((format(printf, 3, 4)));
size_t printfAll(const char* format, ...) __attribute__((format(printf, 2, 3)));
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)));
#ifdef ESP8266
bool text(uint32_t id, const __FlashStringHelper* message);
SendStatus textAll(const __FlashStringHelper* message);
bool binary(uint32_t id, const __FlashStringHelper* message, size_t len);
SendStatus binaryAll(const __FlashStringHelper* message, size_t len);
size_t printf_P(uint32_t id, PGM_P formatP, ...) __attribute__((format(printf, 3, 4)));
size_t printfAll_P(PGM_P formatP, ...) __attribute__((format(printf, 2, 3)));
bool text(uint32_t id, const __FlashStringHelper *message);
SendStatus textAll(const __FlashStringHelper *message);
bool binary(uint32_t id, const __FlashStringHelper *message, size_t len);
SendStatus binaryAll(const __FlashStringHelper *message, size_t len);
size_t printf_P(uint32_t id, PGM_P formatP, ...) __attribute__((format(printf, 3, 4)));
size_t printfAll_P(PGM_P formatP, ...) __attribute__((format(printf, 2, 3)));
#endif
void onEvent(AwsEventHandler handler) { _eventHandler = handler; }
void handleHandshake(AwsHandshakeHandler handler) { _handshakeHandler = handler; }
void onEvent(AwsEventHandler handler) {
_eventHandler = handler;
}
void handleHandshake(AwsHandshakeHandler handler) {
_handshakeHandler = handler;
}
// system callbacks (do not call)
uint32_t _getNextId() { return _cNextId++; }
AsyncWebSocketClient* _newClient(AsyncWebServerRequest* request);
void _handleEvent(AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len);
bool canHandle(AsyncWebServerRequest* request) const override final;
void handleRequest(AsyncWebServerRequest* request) override final;
// system callbacks (do not call)
uint32_t _getNextId() {
return _cNextId++;
}
AsyncWebSocketClient *_newClient(AsyncWebServerRequest *request);
void _handleEvent(AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len);
bool canHandle(AsyncWebServerRequest *request) const override final;
void handleRequest(AsyncWebServerRequest *request) override final;
// messagebuffer functions/objects.
AsyncWebSocketMessageBuffer* makeBuffer(size_t size = 0);
AsyncWebSocketMessageBuffer* makeBuffer(const uint8_t* data, size_t size);
// messagebuffer functions/objects.
AsyncWebSocketMessageBuffer *makeBuffer(size_t size = 0);
AsyncWebSocketMessageBuffer *makeBuffer(const uint8_t *data, size_t size);
std::list<AsyncWebSocketClient>& getClients() { return _clients; }
std::list<AsyncWebSocketClient> &getClients() {
return _clients;
}
};
// WebServer response to authenticate the socket and detach the tcp client from the web server request
class AsyncWebSocketResponse : public AsyncWebServerResponse {
private:
String _content;
AsyncWebSocket* _server;
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; }
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_ */

View file

@ -281,4 +281,4 @@ void SHA1Builder::getBytes(uint8_t *output) {
memcpy(output, hash, SHA1_HASH_SIZE);
}
#endif // ESP_IDF_VERSION_MAJOR < 5
#endif // ESP_IDF_VERSION_MAJOR < 5

View file

@ -24,21 +24,21 @@
#define SHA1_HASH_SIZE 20
class SHA1Builder {
private:
uint32_t total[2]; /* number of bytes processed */
uint32_t state[5]; /* intermediate digest state */
unsigned char buffer[64]; /* data block being processed */
uint8_t hash[SHA1_HASH_SIZE]; /* SHA-1 result */
private:
uint32_t total[2]; /* number of bytes processed */
uint32_t state[5]; /* intermediate digest state */
unsigned char buffer[64]; /* data block being processed */
uint8_t hash[SHA1_HASH_SIZE]; /* SHA-1 result */
void process(const uint8_t* data);
void process(const uint8_t *data);
public:
void begin();
void add(const uint8_t* data, size_t len);
void calculate();
void getBytes(uint8_t* output);
public:
void begin();
void add(const uint8_t *data, size_t len);
void calculate();
void getBytes(uint8_t *output);
};
#endif // SHA1Builder_h
#endif // SHA1Builder_h
#endif // ESP_IDF_VERSION_MAJOR < 5
#endif // ESP_IDF_VERSION_MAJOR < 5

View file

@ -1,7 +1,9 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
#include "ChunkPrint.h"
ChunkPrint::ChunkPrint(uint8_t* destination, size_t from, size_t len)
: _destination(destination), _to_skip(from), _to_write(len), _pos{0} {}
ChunkPrint::ChunkPrint(uint8_t *destination, size_t from, size_t len) : _destination(destination), _to_skip(from), _to_write(len), _pos{0} {}
size_t ChunkPrint::write(uint8_t c) {
if (_to_skip > 0) {
@ -13,4 +15,4 @@ size_t ChunkPrint::write(uint8_t c) {
return 1;
}
return 0;
}
}

View file

@ -1,18 +1,23 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
#ifndef CHUNKPRINT_H
#define CHUNKPRINT_H
#include <Print.h>
class ChunkPrint : public Print {
private:
uint8_t* _destination;
size_t _to_skip;
size_t _to_write;
size_t _pos;
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);
size_t write(uint8_t c);
size_t write(const uint8_t* buffer, size_t size) { return this->Print::write(buffer, size); }
public:
ChunkPrint(uint8_t *destination, size_t from, size_t len);
size_t write(uint8_t c);
size_t write(const uint8_t *buffer, size_t size) {
return this->Print::write(buffer, size);
}
};
#endif

View file

@ -1,70 +1,85 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
#include "WebAuthentication.h"
#include "ESPAsyncWebServer.h"
AsyncMiddlewareChain::~AsyncMiddlewareChain() {
for (AsyncMiddleware* m : _middlewares)
if (m->_freeOnRemoval)
for (AsyncMiddleware *m : _middlewares) {
if (m->_freeOnRemoval) {
delete m;
}
}
}
void AsyncMiddlewareChain::addMiddleware(ArMiddlewareCallback fn) {
AsyncMiddlewareFunction* m = new AsyncMiddlewareFunction(fn);
AsyncMiddlewareFunction *m = new AsyncMiddlewareFunction(fn);
m->_freeOnRemoval = true;
_middlewares.emplace_back(m);
}
void AsyncMiddlewareChain::addMiddleware(AsyncMiddleware* middleware) {
if (middleware)
void AsyncMiddlewareChain::addMiddleware(AsyncMiddleware *middleware) {
if (middleware) {
_middlewares.emplace_back(middleware);
}
}
void AsyncMiddlewareChain::addMiddlewares(std::vector<AsyncMiddleware*> middlewares) {
for (AsyncMiddleware* m : middlewares)
void AsyncMiddlewareChain::addMiddlewares(std::vector<AsyncMiddleware *> middlewares) {
for (AsyncMiddleware *m : middlewares) {
addMiddleware(m);
}
}
bool AsyncMiddlewareChain::removeMiddleware(AsyncMiddleware* middleware) {
bool AsyncMiddlewareChain::removeMiddleware(AsyncMiddleware *middleware) {
// remove all middlewares from _middlewares vector being equal to middleware, delete them having _freeOnRemoval flag to true and resize the vector.
const size_t size = _middlewares.size();
_middlewares.erase(std::remove_if(_middlewares.begin(), _middlewares.end(), [middleware](AsyncMiddleware* m) {
if (m == middleware) {
if (m->_freeOnRemoval)
delete m;
return true;
}
return false;
}),
_middlewares.end());
_middlewares.erase(
std::remove_if(
_middlewares.begin(), _middlewares.end(),
[middleware](AsyncMiddleware *m) {
if (m == middleware) {
if (m->_freeOnRemoval) {
delete m;
}
return true;
}
return false;
}
),
_middlewares.end()
);
return size != _middlewares.size();
}
void AsyncMiddlewareChain::_runChain(AsyncWebServerRequest* request, ArMiddlewareNext finalizer) {
if (!_middlewares.size())
void AsyncMiddlewareChain::_runChain(AsyncWebServerRequest *request, ArMiddlewareNext finalizer) {
if (!_middlewares.size()) {
return finalizer();
}
ArMiddlewareNext next;
std::list<AsyncMiddleware*>::iterator it = _middlewares.begin();
std::list<AsyncMiddleware *>::iterator it = _middlewares.begin();
next = [this, &next, &it, request, finalizer]() {
if (it == _middlewares.end())
if (it == _middlewares.end()) {
return finalizer();
AsyncMiddleware* m = *it;
}
AsyncMiddleware *m = *it;
it++;
return m->run(request, next);
};
return next();
}
void AsyncAuthenticationMiddleware::setUsername(const char* username) {
void AsyncAuthenticationMiddleware::setUsername(const char *username) {
_username = username;
_hasCreds = _username.length() && _credentials.length();
}
void AsyncAuthenticationMiddleware::setPassword(const char* password) {
void AsyncAuthenticationMiddleware::setPassword(const char *password) {
_credentials = password;
_hash = false;
_hasCreds = _username.length() && _credentials.length();
}
void AsyncAuthenticationMiddleware::setPasswordHash(const char* hash) {
void AsyncAuthenticationMiddleware::setPasswordHash(const char *hash) {
_credentials = hash;
_hash = _credentials.length();
_hasCreds = _username.length() && _credentials.length();
@ -72,71 +87,86 @@ void AsyncAuthenticationMiddleware::setPasswordHash(const char* hash) {
bool AsyncAuthenticationMiddleware::generateHash() {
// ensure we have all the necessary data
if (!_hasCreds)
if (!_hasCreds) {
return false;
}
// if we already have a hash, do nothing
if (_hash)
if (_hash) {
return false;
}
switch (_authMethod) {
case AsyncAuthType::AUTH_DIGEST:
_credentials = generateDigestHash(_username.c_str(), _credentials.c_str(), _realm.c_str());
_hash = true;
return true;
if (_credentials.length()) {
_hash = true;
return true;
} else {
return false;
}
case AsyncAuthType::AUTH_BASIC:
_credentials = generateBasicHash(_username.c_str(), _credentials.c_str());
_hash = true;
return true;
if (_credentials.length()) {
_hash = true;
return true;
} else {
return false;
}
default:
return false;
default: return false;
}
}
bool AsyncAuthenticationMiddleware::allowed(AsyncWebServerRequest* request) const {
if (_authMethod == AsyncAuthType::AUTH_NONE)
bool AsyncAuthenticationMiddleware::allowed(AsyncWebServerRequest *request) const {
if (_authMethod == AsyncAuthType::AUTH_NONE) {
return true;
}
if (_authMethod == AsyncAuthType::AUTH_DENIED)
if (_authMethod == AsyncAuthType::AUTH_DENIED) {
return false;
}
if (!_hasCreds)
if (!_hasCreds) {
return true;
}
return request->authenticate(_username.c_str(), _credentials.c_str(), _realm.c_str(), _hash);
}
void AsyncAuthenticationMiddleware::run(AsyncWebServerRequest* request, ArMiddlewareNext next) {
void AsyncAuthenticationMiddleware::run(AsyncWebServerRequest *request, ArMiddlewareNext next) {
return allowed(request) ? next() : request->requestAuthentication(_authMethod, _realm.c_str(), _authFailMsg.c_str());
}
void AsyncHeaderFreeMiddleware::run(AsyncWebServerRequest* request, ArMiddlewareNext next) {
std::vector<const char*> reqHeaders;
request->getHeaderNames(reqHeaders);
for (const char* h : reqHeaders) {
void AsyncHeaderFreeMiddleware::run(AsyncWebServerRequest *request, ArMiddlewareNext next) {
std::list<const char *> toRemove;
for (auto &h : request->getHeaders()) {
bool keep = false;
for (const char* k : _toKeep) {
if (strcasecmp(h, k) == 0) {
for (const char *k : _toKeep) {
if (strcasecmp(h.name().c_str(), k) == 0) {
keep = true;
break;
}
}
if (!keep) {
request->removeHeader(h);
toRemove.push_back(h.name().c_str());
}
}
for (const char *h : toRemove) {
request->removeHeader(h);
}
next();
}
void AsyncHeaderFilterMiddleware::run(AsyncWebServerRequest* request, ArMiddlewareNext next) {
for (auto it = _toRemove.begin(); it != _toRemove.end(); ++it)
void AsyncHeaderFilterMiddleware::run(AsyncWebServerRequest *request, ArMiddlewareNext next) {
for (auto it = _toRemove.begin(); it != _toRemove.end(); ++it) {
request->removeHeader(*it);
}
next();
}
void AsyncLoggingMiddleware::run(AsyncWebServerRequest* request, ArMiddlewareNext next) {
void AsyncLoggingMiddleware::run(AsyncWebServerRequest *request, ArMiddlewareNext next) {
if (!isEnabled()) {
next();
return;
@ -152,7 +182,7 @@ void AsyncLoggingMiddleware::run(AsyncWebServerRequest* request, ArMiddlewareNex
_out->print(request->url().c_str());
_out->print(F(" HTTP/1."));
_out->println(request->version());
for (auto& h : request->getHeaders()) {
for (auto &h : request->getHeaders()) {
if (h.value().length()) {
_out->print('>');
_out->print(' ');
@ -166,7 +196,7 @@ void AsyncLoggingMiddleware::run(AsyncWebServerRequest* request, ArMiddlewareNex
uint32_t elapsed = millis();
next();
elapsed = millis() - elapsed;
AsyncWebServerResponse* response = request->getResponse();
AsyncWebServerResponse *response = request->getResponse();
if (response) {
_out->print(F("* Processed in "));
_out->print(elapsed);
@ -178,7 +208,7 @@ void AsyncLoggingMiddleware::run(AsyncWebServerRequest* request, ArMiddlewareNex
_out->print(response->code());
_out->print(' ');
_out->println(AsyncWebServerResponse::responseCodeToString(response->code()));
for (auto& h : response->getHeaders()) {
for (auto &h : response->getHeaders()) {
if (h.value().length()) {
_out->print('<');
_out->print(' ');
@ -194,7 +224,7 @@ void AsyncLoggingMiddleware::run(AsyncWebServerRequest* request, ArMiddlewareNex
}
}
void AsyncCorsMiddleware::addCORSHeaders(AsyncWebServerResponse* response) {
void AsyncCorsMiddleware::addCORSHeaders(AsyncWebServerResponse *response) {
response->addHeader(asyncsrv::T_CORS_ACAO, _origin.c_str());
response->addHeader(asyncsrv::T_CORS_ACAM, _methods.c_str());
response->addHeader(asyncsrv::T_CORS_ACAH, _headers.c_str());
@ -202,12 +232,12 @@ void AsyncCorsMiddleware::addCORSHeaders(AsyncWebServerResponse* response) {
response->addHeader(asyncsrv::T_CORS_ACMA, String(_maxAge).c_str());
}
void AsyncCorsMiddleware::run(AsyncWebServerRequest* request, ArMiddlewareNext next) {
void AsyncCorsMiddleware::run(AsyncWebServerRequest *request, ArMiddlewareNext next) {
// Origin header ? => CORS handling
if (request->hasHeader(asyncsrv::T_CORS_O)) {
// check if this is a preflight request => handle it and return
if (request->method() == HTTP_OPTIONS) {
AsyncWebServerResponse* response = request->beginResponse(200);
AsyncWebServerResponse *response = request->beginResponse(200);
addCORSHeaders(response);
request->send(response);
return;
@ -215,7 +245,7 @@ void AsyncCorsMiddleware::run(AsyncWebServerRequest* request, ArMiddlewareNext n
// CORS request, no options => let the request pass and add CORS headers after
next();
AsyncWebServerResponse* response = request->getResponse();
AsyncWebServerResponse *response = request->getResponse();
if (response) {
addCORSHeaders(response);
}
@ -226,11 +256,12 @@ void AsyncCorsMiddleware::run(AsyncWebServerRequest* request, ArMiddlewareNext n
}
}
bool AsyncRateLimitMiddleware::isRequestAllowed(uint32_t& retryAfterSeconds) {
bool AsyncRateLimitMiddleware::isRequestAllowed(uint32_t &retryAfterSeconds) {
uint32_t now = millis();
while (!_requestTimes.empty() && _requestTimes.front() <= now - _windowSizeMillis)
while (!_requestTimes.empty() && _requestTimes.front() <= now - _windowSizeMillis) {
_requestTimes.pop_front();
}
_requestTimes.push_back(now);
@ -244,12 +275,12 @@ bool AsyncRateLimitMiddleware::isRequestAllowed(uint32_t& retryAfterSeconds) {
return true;
}
void AsyncRateLimitMiddleware::run(AsyncWebServerRequest* request, ArMiddlewareNext next) {
void AsyncRateLimitMiddleware::run(AsyncWebServerRequest *request, ArMiddlewareNext next) {
uint32_t retryAfterSeconds;
if (isRequestAllowed(retryAfterSeconds)) {
next();
} else {
AsyncWebServerResponse* response = request->beginResponse(429);
AsyncWebServerResponse *response = request->beginResponse(429);
response->addHeader(asyncsrv::T_retry_after, retryAfterSeconds);
request->send(response);
}

View file

@ -1,29 +1,12 @@
/*
Asynchronous WebServer library for Espressif MCUs
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
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>
#if defined(ESP32) || defined(TARGET_RP2040)
#include <MD5Builder.h>
#if defined(ESP32) || defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <MD5Builder.h>
#else
#include "md5.h"
#include "md5.h"
#endif
#include "literals.h"
@ -31,23 +14,25 @@ using namespace asyncsrv;
// Basic Auth hash = base64("username:password")
bool checkBasicAuthentication(const char* hash, const char* username, const char* password) {
if (username == NULL || password == NULL || hash == NULL)
bool checkBasicAuthentication(const char *hash, const char *username, const char *password) {
if (username == NULL || password == NULL || hash == NULL) {
return false;
}
return generateBasicHash(username, password).equalsIgnoreCase(hash);
}
String generateBasicHash(const char* username, const char* password) {
if (username == NULL || password == NULL)
String generateBasicHash(const char *username, const char *password) {
if (username == NULL || password == NULL) {
return emptyString;
}
size_t toencodeLen = strlen(username) + strlen(password) + 1;
char* toencode = new char[toencodeLen + 1];
char *toencode = new char[toencodeLen + 1];
if (toencode == NULL) {
return emptyString;
}
char* encoded = new char[base64_encode_expected_len(toencodeLen) + 1];
char *encoded = new char[base64_encode_expected_len(toencodeLen) + 1];
if (encoded == NULL) {
delete[] toencode;
return emptyString;
@ -64,8 +49,8 @@ String generateBasicHash(const char* username, const char* password) {
return emptyString;
}
static bool getMD5(uint8_t* data, uint16_t len, char* output) { // 33 bytes or more
#if defined(ESP32) || defined(TARGET_RP2040)
static bool getMD5(uint8_t *data, uint16_t len, char *output) { // 33 bytes or more
#if defined(ESP32) || defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
MD5Builder md5;
md5.begin();
md5.add(data, len);
@ -74,9 +59,10 @@ static bool getMD5(uint8_t* data, uint16_t len, char* output) { // 33 bytes or m
#else
md5_context_t _ctx;
uint8_t* _buf = (uint8_t*)malloc(16);
if (_buf == NULL)
uint8_t *_buf = (uint8_t *)malloc(16);
if (_buf == NULL) {
return false;
}
memset(_buf, 0x00, 16);
MD5Init(&_ctx);
@ -98,49 +84,77 @@ String genRandomMD5() {
#else
uint32_t r = rand();
#endif
char* out = (char*)malloc(33);
if (out == NULL || !getMD5((uint8_t*)(&r), 4, out))
char *out = (char *)malloc(33);
if (out == NULL || !getMD5((uint8_t *)(&r), 4, out)) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
return emptyString;
}
String res = String(out);
free(out);
return res;
}
static String stringMD5(const String& in) {
char* out = (char*)malloc(33);
if (out == NULL || !getMD5((uint8_t*)(in.c_str()), in.length(), out))
static String stringMD5(const String &in) {
char *out = (char *)malloc(33);
if (out == NULL || !getMD5((uint8_t *)(in.c_str()), in.length(), out)) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
return emptyString;
}
String res = String(out);
free(out);
return res;
}
String generateDigestHash(const char* username, const char* password, const char* realm) {
String generateDigestHash(const char *username, const char *password, const char *realm) {
if (username == NULL || password == NULL || realm == NULL) {
return emptyString;
}
char* out = (char*)malloc(33);
char *out = (char *)malloc(33);
if (out == NULL) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
return emptyString;
}
String in;
in.reserve(strlen(username) + strlen(realm) + strlen(password) + 2);
if (!in.reserve(strlen(username) + strlen(realm) + strlen(password) + 2)) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
free(out);
return emptyString;
}
in.concat(username);
in.concat(':');
in.concat(realm);
in.concat(':');
in.concat(password);
if (out == NULL || !getMD5((uint8_t*)(in.c_str()), in.length(), out))
if (!getMD5((uint8_t *)(in.c_str()), in.length(), out)) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
free(out);
return emptyString;
}
in = String(out);
free(out);
return in;
}
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)
{
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");
// os_printf("AUTH FAIL: missing required fields\n");
return false;
}
@ -160,8 +174,8 @@ bool checkDigestAuthentication(const char* header, const char* method, const cha
String myNc;
String myCnonce;
myHeader += (char)0x2c; // ','
myHeader += (char)0x20; // ' '
myHeader += (char)0x2c; // ','
myHeader += (char)0x20; // ' '
do {
String avLine(myHeader.substring(0, nextBreak));
avLine.trim();

View file

@ -1,37 +1,22 @@
/*
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
*/
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
#ifndef WEB_AUTHENTICATION_H_
#define WEB_AUTHENTICATION_H_
#include "Arduino.h"
bool checkBasicAuthentication(const char* header, const char* username, const char* password);
bool checkBasicAuthentication(const char *header, const char *username, const char *password);
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);
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);
String generateDigestHash(const char *username, const char *password, const char *realm);
String generateBasicHash(const char* username, const char* password);
String generateBasicHash(const char *username, const char *password);
String genRandomMD5();

View file

@ -1,101 +1,94 @@
/*
Asynchronous WebServer library for Espressif MCUs
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
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>
#include <regex>
#endif
#include "stddef.h"
#include <time.h>
class AsyncStaticWebHandler : public AsyncWebHandler {
using File = fs::File;
using FS = fs::FS;
using File = fs::File;
using FS = fs::FS;
private:
bool _getFile(AsyncWebServerRequest* request) const;
bool _searchFile(AsyncWebServerRequest* request, const String& path);
uint8_t _countBits(const uint8_t value) const;
private:
bool _getFile(AsyncWebServerRequest *request) const;
bool _searchFile(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 _tryGzipFirst = true;
protected:
FS _fs;
String _uri;
String _path;
String _default_file;
String _cache_control;
String _last_modified;
AwsTemplateProcessor _callback;
bool _isDir;
bool _tryGzipFirst = true;
public:
AsyncStaticWebHandler(const char* uri, FS& fs, const char* path, const char* cache_control);
bool canHandle(AsyncWebServerRequest* request) const override final;
void handleRequest(AsyncWebServerRequest* request) override final;
AsyncStaticWebHandler& setTryGzipFirst(bool value);
AsyncStaticWebHandler& setIsDir(bool isDir);
AsyncStaticWebHandler& setDefaultFile(const char* filename);
AsyncStaticWebHandler& setCacheControl(const char* cache_control);
public:
AsyncStaticWebHandler(const char *uri, FS &fs, const char *path, const char *cache_control);
bool canHandle(AsyncWebServerRequest *request) const override final;
void handleRequest(AsyncWebServerRequest *request) override final;
AsyncStaticWebHandler &setTryGzipFirst(bool value);
AsyncStaticWebHandler &setIsDir(bool isDir);
AsyncStaticWebHandler &setDefaultFile(const char *filename);
AsyncStaticWebHandler &setCacheControl(const char *cache_control);
/**
/**
* @brief Set the Last-Modified time for the object
*
* @param last_modified
* @return AsyncStaticWebHandler&
*
* @param last_modified
* @return AsyncStaticWebHandler&
*/
AsyncStaticWebHandler& setLastModified(const char* last_modified);
AsyncStaticWebHandler& setLastModified(struct tm* last_modified);
AsyncStaticWebHandler& setLastModified(time_t last_modified);
// sets to current time. Make sure sntp is runing and time is updated
AsyncStaticWebHandler& setLastModified();
AsyncStaticWebHandler &setLastModified(const char *last_modified);
AsyncStaticWebHandler &setLastModified(struct tm *last_modified);
AsyncStaticWebHandler &setLastModified(time_t last_modified);
// sets to current time. Make sure sntp is running and time is updated
AsyncStaticWebHandler &setLastModified();
AsyncStaticWebHandler& setTemplateProcessor(AwsTemplateProcessor newCallback);
AsyncStaticWebHandler &setTemplateProcessor(AwsTemplateProcessor newCallback);
};
class AsyncCallbackWebHandler : public AsyncWebHandler {
private:
protected:
String _uri;
WebRequestMethodComposite _method;
ArRequestHandlerFunction _onRequest;
ArUploadHandlerFunction _onUpload;
ArBodyHandlerFunction _onBody;
bool _isRegex;
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);
void setMethod(WebRequestMethodComposite method) { _method = method; }
void onRequest(ArRequestHandlerFunction fn) { _onRequest = fn; }
void onUpload(ArUploadHandlerFunction fn) { _onUpload = fn; }
void onBody(ArBodyHandlerFunction fn) { _onBody = fn; }
public:
AsyncCallbackWebHandler() : _uri(), _method(HTTP_ANY), _onRequest(NULL), _onUpload(NULL), _onBody(NULL), _isRegex(false) {}
void setUri(const String &uri);
void setMethod(WebRequestMethodComposite method) {
_method = method;
}
void onRequest(ArRequestHandlerFunction fn) {
_onRequest = fn;
}
void onUpload(ArUploadHandlerFunction fn) {
_onUpload = fn;
}
void onBody(ArBodyHandlerFunction fn) {
_onBody = fn;
}
bool canHandle(AsyncWebServerRequest* request) const override final;
void handleRequest(AsyncWebServerRequest* request) override final;
void handleUpload(AsyncWebServerRequest* request, const String& filename, size_t index, uint8_t* data, size_t len, bool final) override final;
void handleBody(AsyncWebServerRequest* request, uint8_t* data, size_t len, size_t index, size_t total) override final;
bool isRequestHandlerTrivial() const override final { return !_onRequest; }
bool canHandle(AsyncWebServerRequest *request) const override final;
void handleRequest(AsyncWebServerRequest *request) override final;
void handleUpload(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final) override final;
void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override final;
bool isRequestHandlerTrivial() const override final {
return !_onRequest;
}
};
#endif /* ASYNCWEBSERVERHANDLERIMPL_H_ */

View file

@ -1,33 +1,16 @@
/*
Asynchronous WebServer library for Espressif MCUs
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
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"
using namespace asyncsrv;
AsyncWebHandler& AsyncWebHandler::setFilter(ArRequestFilterFunction fn) {
AsyncWebHandler &AsyncWebHandler::setFilter(ArRequestFilterFunction fn) {
_filter = fn;
return *this;
}
AsyncWebHandler& AsyncWebHandler::setAuthentication(const char* username, const char* password, AsyncAuthType authMethod) {
AsyncWebHandler &AsyncWebHandler::setAuthentication(const char *username, const char *password, AsyncAuthType authMethod) {
if (!_authMiddleware) {
_authMiddleware = new AsyncAuthenticationMiddleware();
_authMiddleware->_freeOnRemoval = true;
@ -39,13 +22,15 @@ AsyncWebHandler& AsyncWebHandler::setAuthentication(const char* username, const
return *this;
};
AsyncStaticWebHandler::AsyncStaticWebHandler(const char* uri, FS& fs, const char* path, const char* cache_control)
: _fs(fs), _uri(uri), _path(path), _default_file(F("index.htm")), _cache_control(cache_control), _last_modified(), _callback(nullptr) {
AsyncStaticWebHandler::AsyncStaticWebHandler(const char *uri, FS &fs, const char *path, const char *cache_control)
: _fs(fs), _uri(uri), _path(path), _default_file(F("index.htm")), _cache_control(cache_control), _last_modified(), _callback(nullptr) {
// Ensure leading '/'
if (_uri.length() == 0 || _uri[0] != '/')
if (_uri.length() == 0 || _uri[0] != '/') {
_uri = String('/') + _uri;
if (_path.length() == 0 || _path[0] != '/')
}
if (_path.length() == 0 || _path[0] != '/') {
_path = String('/') + _path;
}
// If path ends with '/' we assume a hint that this is a directory to improve performance.
// However - if it does not end with '/' we, can't assume a file, path can still be a directory.
@ -53,45 +38,47 @@ AsyncStaticWebHandler::AsyncStaticWebHandler(const char* uri, FS& fs, const char
// Remove the trailing '/' so we can handle default file
// Notice that root will be "" not "/"
if (_uri[_uri.length() - 1] == '/')
if (_uri[_uri.length() - 1] == '/') {
_uri = _uri.substring(0, _uri.length() - 1);
if (_path[_path.length() - 1] == '/')
}
if (_path[_path.length() - 1] == '/') {
_path = _path.substring(0, _path.length() - 1);
}
}
AsyncStaticWebHandler& AsyncStaticWebHandler::setTryGzipFirst(bool value) {
AsyncStaticWebHandler &AsyncStaticWebHandler::setTryGzipFirst(bool value) {
_tryGzipFirst = value;
return *this;
}
AsyncStaticWebHandler& AsyncStaticWebHandler::setIsDir(bool isDir) {
AsyncStaticWebHandler &AsyncStaticWebHandler::setIsDir(bool isDir) {
_isDir = isDir;
return *this;
}
AsyncStaticWebHandler& AsyncStaticWebHandler::setDefaultFile(const char* filename) {
AsyncStaticWebHandler &AsyncStaticWebHandler::setDefaultFile(const char *filename) {
_default_file = filename;
return *this;
}
AsyncStaticWebHandler& AsyncStaticWebHandler::setCacheControl(const char* cache_control) {
AsyncStaticWebHandler &AsyncStaticWebHandler::setCacheControl(const char *cache_control) {
_cache_control = cache_control;
return *this;
}
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(const char* last_modified) {
AsyncStaticWebHandler &AsyncStaticWebHandler::setLastModified(const char *last_modified) {
_last_modified = last_modified;
return *this;
}
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(struct tm* last_modified) {
AsyncStaticWebHandler &AsyncStaticWebHandler::setLastModified(struct tm *last_modified) {
char result[30];
#ifdef ESP8266
auto formatP = PSTR("%a, %d %b %Y %H:%M:%S GMT");
char format[strlen_P(formatP) + 1];
strcpy_P(format, formatP);
#else
static constexpr const char* format = "%a, %d %b %Y %H:%M:%S GMT";
static constexpr const char *format = "%a, %d %b %Y %H:%M:%S GMT";
#endif
strftime(result, sizeof(result), format, last_modified);
@ -99,22 +86,23 @@ AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(struct tm* last_mo
return *this;
}
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(time_t last_modified) {
return setLastModified((struct tm*)gmtime(&last_modified));
AsyncStaticWebHandler &AsyncStaticWebHandler::setLastModified(time_t last_modified) {
return setLastModified((struct tm *)gmtime(&last_modified));
}
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified() {
AsyncStaticWebHandler &AsyncStaticWebHandler::setLastModified() {
time_t last_modified;
if (time(&last_modified) == 0) // time is not yet set
if (time(&last_modified) == 0) { // time is not yet set
return *this;
}
return setLastModified(last_modified);
}
bool AsyncStaticWebHandler::canHandle(AsyncWebServerRequest* request) const {
bool AsyncStaticWebHandler::canHandle(AsyncWebServerRequest *request) const {
return request->isHTTP() && request->method() == HTTP_GET && request->url().startsWith(_uri) && _getFile(request);
}
bool AsyncStaticWebHandler::_getFile(AsyncWebServerRequest* request) const {
bool AsyncStaticWebHandler::_getFile(AsyncWebServerRequest *request) const {
// Remove the found uri
String path = request->url().substring(_uri.length());
@ -124,28 +112,31 @@ bool AsyncStaticWebHandler::_getFile(AsyncWebServerRequest* request) const {
path = _path + path;
// Do we have a file or .gz file
if (!canSkipFileCheck && const_cast<AsyncStaticWebHandler*>(this)->_searchFile(request, path))
if (!canSkipFileCheck && const_cast<AsyncStaticWebHandler *>(this)->_searchFile(request, path)) {
return true;
}
// Can't handle if not default file
if (_default_file.length() == 0)
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] != '/')
// Try to add default file, ensure there is a trailing '/' to the path.
if (path.length() == 0 || path[path.length() - 1] != '/') {
path += String('/');
}
path += _default_file;
return const_cast<AsyncStaticWebHandler*>(this)->_searchFile(request, path);
return const_cast<AsyncStaticWebHandler *>(this)->_searchFile(request, path);
}
#ifdef ESP32
#define FILE_IS_REAL(f) (f == true && !f.isDirectory())
#define FILE_IS_REAL(f) (f == true && !f.isDirectory())
#else
#define FILE_IS_REAL(f) (f == true)
#define FILE_IS_REAL(f) (f == true)
#endif
bool AsyncStaticWebHandler::_searchFile(AsyncWebServerRequest* request, const String& path) {
bool AsyncStaticWebHandler::_searchFile(AsyncWebServerRequest *request, const String &path) {
bool fileFound = false;
bool gzipFound = false;
@ -180,9 +171,17 @@ bool AsyncStaticWebHandler::_searchFile(AsyncWebServerRequest* request, const St
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);
char *_tempPath = (char *)malloc(pathLen + 1);
if (_tempPath == NULL) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
request->abort();
request->_tempFile.close();
return false;
}
snprintf_P(_tempPath, pathLen + 1, PSTR("%s"), path.c_str());
request->_tempObject = (void*)_tempPath;
request->_tempObject = (void *)_tempPath;
}
return found;
@ -191,81 +190,97 @@ bool AsyncStaticWebHandler::_searchFile(AsyncWebServerRequest* request, const St
uint8_t AsyncStaticWebHandler::_countBits(const uint8_t value) const {
uint8_t w = value;
uint8_t n;
for (n = 0; w != 0; n++)
for (n = 0; w != 0; n++) {
w &= w - 1;
}
return n;
}
void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest* request) {
void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest *request) {
// Get the filename from request->_tempObject and free it
String filename((char*)request->_tempObject);
String filename((char *)request->_tempObject);
free(request->_tempObject);
request->_tempObject = NULL;
if (request->_tempFile != true){
if (request->_tempFile != true) {
request->send(404);
return;
}
time_t lw = request->_tempFile.getLastWrite(); // get last file mod time (if supported by FS)
// set etag to lastmod timestamp if available, otherwise to size
String etag;
if (lw) {
setLastModified(lw);
#if defined(TARGET_RP2040)
// time_t == long long int
constexpr size_t len = 1 + 8 * sizeof(time_t);
char buf[len];
char* ret = lltoa(lw ^ request->_tempFile.size(), buf, len, 10);
etag = ret ? String(ret) : String(request->_tempFile.size());
time_t lw = request->_tempFile.getLastWrite(); // get last file mod time (if supported by FS)
// set etag to lastmod timestamp if available, otherwise to size
String etag;
if (lw) {
setLastModified(lw);
#if defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
// time_t == long long int
constexpr size_t len = 1 + 8 * sizeof(time_t);
char buf[len];
char *ret = lltoa(lw ^ request->_tempFile.size(), buf, len, 10);
etag = ret ? String(ret) : String(request->_tempFile.size());
#else
etag = lw ^ request->_tempFile.size(); // etag combines file size and lastmod timestamp
etag = lw ^ request->_tempFile.size(); // etag combines file size and lastmod timestamp
#endif
} else {
etag = request->_tempFile.size();
}
} else {
#if defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
etag = String(request->_tempFile.size());
#else
etag = request->_tempFile.size();
#endif
}
bool not_modified = false;
bool not_modified = false;
// if-none-match has precedence over if-modified-since
if (request->hasHeader(T_INM))
not_modified = request->header(T_INM).equals(etag);
else if (_last_modified.length())
not_modified = request->header(T_IMS).equals(_last_modified);
// if-none-match has precedence over if-modified-since
if (request->hasHeader(T_INM)) {
not_modified = request->header(T_INM).equals(etag);
} else if (_last_modified.length()) {
not_modified = request->header(T_IMS).equals(_last_modified);
}
AsyncWebServerResponse* response;
AsyncWebServerResponse *response;
if (not_modified){
request->_tempFile.close();
response = new AsyncBasicResponse(304); // Not modified
} else {
response = new AsyncFileResponse(request->_tempFile, filename, emptyString, false, _callback);
}
if (not_modified) {
request->_tempFile.close();
response = new AsyncBasicResponse(304); // Not modified
} else {
response = new AsyncFileResponse(request->_tempFile, filename, emptyString, false, _callback);
}
response->addHeader(T_ETag, etag.c_str());
if (!response) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
request->abort();
return;
}
if (_last_modified.length())
response->addHeader(T_Last_Modified, _last_modified.c_str());
if (_cache_control.length())
response->addHeader(T_Cache_Control, _cache_control.c_str());
request->send(response);
response->addHeader(T_ETag, etag.c_str());
if (_last_modified.length()) {
response->addHeader(T_Last_Modified, _last_modified.c_str());
}
if (_cache_control.length()) {
response->addHeader(T_Cache_Control, _cache_control.c_str());
}
request->send(response);
}
AsyncStaticWebHandler& AsyncStaticWebHandler::setTemplateProcessor(AwsTemplateProcessor newCallback) {
AsyncStaticWebHandler &AsyncStaticWebHandler::setTemplateProcessor(AwsTemplateProcessor newCallback) {
_callback = newCallback;
return *this;
}
void AsyncCallbackWebHandler::setUri(const String& uri) {
void AsyncCallbackWebHandler::setUri(const String &uri) {
_uri = uri;
_isRegex = uri.startsWith("^") && uri.endsWith("$");
}
bool AsyncCallbackWebHandler::canHandle(AsyncWebServerRequest* request) const {
if (!_onRequest || !request->isHTTP() || !(_method & request->method()))
bool AsyncCallbackWebHandler::canHandle(AsyncWebServerRequest *request) const {
if (!_onRequest || !request->isHTTP() || !(_method & request->method())) {
return false;
}
#ifdef ASYNCWEBSERVER_REGEX
if (_isRegex) {
@ -273,7 +288,7 @@ bool AsyncCallbackWebHandler::canHandle(AsyncWebServerRequest* request) const {
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
for (size_t i = 1; i < matches.size(); ++i) { // start from 1
request->_addPathParam(matches[i].str().c_str());
}
} else {
@ -284,31 +299,37 @@ bool AsyncCallbackWebHandler::canHandle(AsyncWebServerRequest* request) const {
if (_uri.length() && _uri.startsWith("/*.")) {
String uriTemplate = String(_uri);
uriTemplate = uriTemplate.substring(uriTemplate.lastIndexOf("."));
if (!request->url().endsWith(uriTemplate))
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))
if (!request->url().startsWith(uriTemplate)) {
return false;
} else if (_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri + "/")))
}
} else if (_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri + "/"))) {
return false;
}
return true;
}
void AsyncCallbackWebHandler::handleRequest(AsyncWebServerRequest* request) {
if (_onRequest)
void AsyncCallbackWebHandler::handleRequest(AsyncWebServerRequest *request) {
if (_onRequest) {
_onRequest(request);
else
request->send(500);
} else {
request->send(404, T_text_plain, "Not found");
}
}
void AsyncCallbackWebHandler::handleUpload(AsyncWebServerRequest* request, const String& filename, size_t index, uint8_t* data, size_t len, bool final) {
if (_onUpload)
void AsyncCallbackWebHandler::handleUpload(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final) {
if (_onUpload) {
_onUpload(request, filename, index, data, len, final);
}
}
void AsyncCallbackWebHandler::handleBody(AsyncWebServerRequest* request, uint8_t* data, size_t len, size_t index, size_t total) {
void AsyncCallbackWebHandler::handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
// ESP_LOGD("AsyncWebServer", "AsyncCallbackWebHandler::handleBody");
if (_onBody)
if (_onBody) {
_onBody(request, data, len, index, total);
}
}
}

View file

@ -1,30 +1,13 @@
/*
Asynchronous WebServer library for Espressif MCUs
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
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
// arduino is not compatible with std::vector
#undef min
#undef max
#endif
#include "literals.h"
#include <StreamString.h>
@ -34,129 +17,158 @@
// 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;
private:
String _content;
public:
explicit AsyncBasicResponse(int code, const char* contentType = asyncsrv::empty, const char* content = asyncsrv::empty);
AsyncBasicResponse(int code, const String& contentType, const String& content = emptyString) : AsyncBasicResponse(code, contentType.c_str(), content.c_str()) {}
void _respond(AsyncWebServerRequest* request) override final;
size_t _ack(AsyncWebServerRequest* request, size_t len, uint32_t time) override final;
bool _sourceValid() const override final { return true; }
public:
explicit AsyncBasicResponse(int code, const char *contentType = asyncsrv::empty, const char *content = asyncsrv::empty);
AsyncBasicResponse(int code, const String &contentType, const String &content = emptyString)
: AsyncBasicResponse(code, contentType.c_str(), content.c_str()) {}
void _respond(AsyncWebServerRequest *request) override final;
size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time) override final;
bool _sourceValid() const override final {
return true;
}
};
class AsyncAbstractResponse : public AsyncWebServerResponse {
private:
private:
#if ASYNCWEBSERVER_USE_CHUNK_INFLIGHT
// amount of responce data in-flight, i.e. sent, but not acked yet
size_t _in_flight{0};
// in-flight queue credits
size_t _in_flight_credit{2};
// amount of response data in-flight, i.e. sent, but not acked yet
size_t _in_flight{0};
// in-flight queue credits
size_t _in_flight_credit{2};
#endif
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);
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;
protected:
AwsTemplateProcessor _callback;
public:
AsyncAbstractResponse(AwsTemplateProcessor callback = nullptr);
virtual ~AsyncAbstractResponse() {}
void _respond(AsyncWebServerRequest* request) override final;
size_t _ack(AsyncWebServerRequest* request, size_t len, uint32_t time) override final;
virtual bool _sourceValid() const { return false; }
virtual size_t _fillBuffer(uint8_t* buf __attribute__((unused)), size_t maxLen __attribute__((unused))) { return 0; }
public:
AsyncAbstractResponse(AwsTemplateProcessor callback = nullptr);
virtual ~AsyncAbstractResponse() {}
void _respond(AsyncWebServerRequest *request) override final;
size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time) override final;
virtual 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 '%'
#define TEMPLATE_PLACEHOLDER '%'
#endif
#define TEMPLATE_PARAM_NAME_LENGTH 32
class AsyncFileResponse : public AsyncAbstractResponse {
using File = fs::File;
using FS = fs::FS;
using File = fs::File;
using FS = fs::FS;
private:
File _content;
String _path;
void _setContentTypeFromPath(const String& path);
private:
File _content;
String _path;
void _setContentTypeFromPath(const String &path);
public:
AsyncFileResponse(FS& fs, const String& path, const char* contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr);
AsyncFileResponse(FS& fs, const String& path, const String& contentType, bool download = false, AwsTemplateProcessor callback = nullptr) : AsyncFileResponse(fs, path, contentType.c_str(), download, callback) {}
AsyncFileResponse(File content, const String& path, const char* contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr);
AsyncFileResponse(File content, const String& path, const String& contentType, bool download = false, AwsTemplateProcessor callack = nullptr) : AsyncFileResponse(content, path, contentType.c_str(), download, callack) {}
~AsyncFileResponse() { _content.close(); }
bool _sourceValid() const override final { return !!(_content); }
size_t _fillBuffer(uint8_t* buf, size_t maxLen) override final;
public:
AsyncFileResponse(FS &fs, const String &path, const char *contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr);
AsyncFileResponse(FS &fs, const String &path, const String &contentType, bool download = false, AwsTemplateProcessor callback = nullptr)
: AsyncFileResponse(fs, path, contentType.c_str(), download, callback) {}
AsyncFileResponse(
File content, const String &path, const char *contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr
);
AsyncFileResponse(File content, const String &path, const String &contentType, bool download = false, AwsTemplateProcessor callback = nullptr)
: AsyncFileResponse(content, path, contentType.c_str(), download, callback) {}
~AsyncFileResponse() {
_content.close();
}
bool _sourceValid() const override final {
return !!(_content);
}
size_t _fillBuffer(uint8_t *buf, size_t maxLen) override final;
};
class AsyncStreamResponse : public AsyncAbstractResponse {
private:
Stream* _content;
private:
Stream *_content;
public:
AsyncStreamResponse(Stream& stream, const char* contentType, size_t len, AwsTemplateProcessor callback = nullptr);
AsyncStreamResponse(Stream& stream, const String& contentType, size_t len, AwsTemplateProcessor callback = nullptr) : AsyncStreamResponse(stream, contentType.c_str(), len, callback) {}
bool _sourceValid() const override final { return !!(_content); }
size_t _fillBuffer(uint8_t* buf, size_t maxLen) override final;
public:
AsyncStreamResponse(Stream &stream, const char *contentType, size_t len, AwsTemplateProcessor callback = nullptr);
AsyncStreamResponse(Stream &stream, const String &contentType, size_t len, AwsTemplateProcessor callback = nullptr)
: AsyncStreamResponse(stream, contentType.c_str(), len, callback) {}
bool _sourceValid() const override final {
return !!(_content);
}
size_t _fillBuffer(uint8_t *buf, size_t maxLen) override final;
};
class AsyncCallbackResponse : public AsyncAbstractResponse {
private:
AwsResponseFiller _content;
size_t _filledLength;
private:
AwsResponseFiller _content;
size_t _filledLength;
public:
AsyncCallbackResponse(const char* contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr);
AsyncCallbackResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) : AsyncCallbackResponse(contentType.c_str(), len, callback, templateCallback) {}
bool _sourceValid() const override final { return !!(_content); }
size_t _fillBuffer(uint8_t* buf, size_t maxLen) override final;
public:
AsyncCallbackResponse(const char *contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr);
AsyncCallbackResponse(const String &contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr)
: AsyncCallbackResponse(contentType.c_str(), len, callback, templateCallback) {}
bool _sourceValid() const override final {
return !!(_content);
}
size_t _fillBuffer(uint8_t *buf, size_t maxLen) override final;
};
class AsyncChunkedResponse : public AsyncAbstractResponse {
private:
AwsResponseFiller _content;
size_t _filledLength;
private:
AwsResponseFiller _content;
size_t _filledLength;
public:
AsyncChunkedResponse(const char* contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr);
AsyncChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) : AsyncChunkedResponse(contentType.c_str(), callback, templateCallback) {}
bool _sourceValid() const override final { return !!(_content); }
size_t _fillBuffer(uint8_t* buf, size_t maxLen) override final;
public:
AsyncChunkedResponse(const char *contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr);
AsyncChunkedResponse(const String &contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr)
: AsyncChunkedResponse(contentType.c_str(), callback, templateCallback) {}
bool _sourceValid() const override final {
return !!(_content);
}
size_t _fillBuffer(uint8_t *buf, size_t maxLen) override final;
};
class AsyncProgmemResponse : public AsyncAbstractResponse {
private:
const uint8_t* _content;
size_t _readLength;
private:
const uint8_t *_content;
size_t _readLength;
public:
AsyncProgmemResponse(int code, const char* contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr);
AsyncProgmemResponse(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr) : AsyncProgmemResponse(code, contentType.c_str(), content, len, callback) {}
bool _sourceValid() const override final { return true; }
size_t _fillBuffer(uint8_t* buf, size_t maxLen) override final;
public:
AsyncProgmemResponse(int code, const char *contentType, const uint8_t *content, size_t len, AwsTemplateProcessor callback = nullptr);
AsyncProgmemResponse(int code, const String &contentType, const uint8_t *content, size_t len, AwsTemplateProcessor callback = nullptr)
: AsyncProgmemResponse(code, contentType.c_str(), content, len, callback) {}
bool _sourceValid() const override final {
return true;
}
size_t _fillBuffer(uint8_t *buf, size_t maxLen) override final;
};
class AsyncResponseStream : public AsyncAbstractResponse, public Print {
private:
StreamString _content;
private:
StreamString _content;
public:
AsyncResponseStream(const char* contentType, size_t bufferSize);
AsyncResponseStream(const String& contentType, size_t bufferSize) : AsyncResponseStream(contentType.c_str(), bufferSize) {}
bool _sourceValid() const override final { return (_state < RESPONSE_END); }
size_t _fillBuffer(uint8_t* buf, size_t maxLen) override final;
size_t write(const uint8_t* data, size_t len);
size_t write(uint8_t data);
using Print::write;
public:
AsyncResponseStream(const char *contentType, size_t bufferSize);
AsyncResponseStream(const String &contentType, size_t bufferSize) : AsyncResponseStream(contentType.c_str(), bufferSize) {}
bool _sourceValid() const override final {
return (_state < RESPONSE_END);
}
size_t _fillBuffer(uint8_t *buf, size_t maxLen) override final;
size_t write(const uint8_t *data, size_t len);
size_t write(uint8_t data);
using Print::write;
};
#endif /* ASYNCWEBSERVERRESPONSEIMPL_H_ */

View file

@ -1,34 +1,19 @@
/*
Asynchronous WebServer library for Espressif MCUs
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
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"
using namespace asyncsrv;
// Since ESP8266 does not link memchr by default, here's its implementation.
void* memchr(void* ptr, int ch, size_t count) {
unsigned char* p = static_cast<unsigned char*>(ptr);
while (count--)
if (*p++ == static_cast<unsigned char>(ch))
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;
}
@ -37,120 +22,95 @@ void* memchr(void* ptr, int ch, size_t count) {
*
*/
const char* AsyncWebServerResponse::responseCodeToString(int code) {
const char *AsyncWebServerResponse::responseCodeToString(int code) {
switch (code) {
case 100:
return T_HTTP_CODE_100;
case 101:
return T_HTTP_CODE_101;
case 200:
return T_HTTP_CODE_200;
case 201:
return T_HTTP_CODE_201;
case 202:
return T_HTTP_CODE_202;
case 203:
return T_HTTP_CODE_203;
case 204:
return T_HTTP_CODE_204;
case 205:
return T_HTTP_CODE_205;
case 206:
return T_HTTP_CODE_206;
case 300:
return T_HTTP_CODE_300;
case 301:
return T_HTTP_CODE_301;
case 302:
return T_HTTP_CODE_302;
case 303:
return T_HTTP_CODE_303;
case 304:
return T_HTTP_CODE_304;
case 305:
return T_HTTP_CODE_305;
case 307:
return T_HTTP_CODE_307;
case 400:
return T_HTTP_CODE_400;
case 401:
return T_HTTP_CODE_401;
case 402:
return T_HTTP_CODE_402;
case 403:
return T_HTTP_CODE_403;
case 404:
return T_HTTP_CODE_404;
case 405:
return T_HTTP_CODE_405;
case 406:
return T_HTTP_CODE_406;
case 407:
return T_HTTP_CODE_407;
case 408:
return T_HTTP_CODE_408;
case 409:
return T_HTTP_CODE_409;
case 410:
return T_HTTP_CODE_410;
case 411:
return T_HTTP_CODE_411;
case 412:
return T_HTTP_CODE_412;
case 413:
return T_HTTP_CODE_413;
case 414:
return T_HTTP_CODE_414;
case 415:
return T_HTTP_CODE_415;
case 416:
return T_HTTP_CODE_416;
case 417:
return T_HTTP_CODE_417;
case 429:
return T_HTTP_CODE_429;
case 500:
return T_HTTP_CODE_500;
case 501:
return T_HTTP_CODE_501;
case 502:
return T_HTTP_CODE_502;
case 503:
return T_HTTP_CODE_503;
case 504:
return T_HTTP_CODE_504;
case 505:
return T_HTTP_CODE_505;
default:
return T_HTTP_CODE_ANY;
case 100: return T_HTTP_CODE_100;
case 101: return T_HTTP_CODE_101;
case 200: return T_HTTP_CODE_200;
case 201: return T_HTTP_CODE_201;
case 202: return T_HTTP_CODE_202;
case 203: return T_HTTP_CODE_203;
case 204: return T_HTTP_CODE_204;
case 205: return T_HTTP_CODE_205;
case 206: return T_HTTP_CODE_206;
case 300: return T_HTTP_CODE_300;
case 301: return T_HTTP_CODE_301;
case 302: return T_HTTP_CODE_302;
case 303: return T_HTTP_CODE_303;
case 304: return T_HTTP_CODE_304;
case 305: return T_HTTP_CODE_305;
case 307: return T_HTTP_CODE_307;
case 400: return T_HTTP_CODE_400;
case 401: return T_HTTP_CODE_401;
case 402: return T_HTTP_CODE_402;
case 403: return T_HTTP_CODE_403;
case 404: return T_HTTP_CODE_404;
case 405: return T_HTTP_CODE_405;
case 406: return T_HTTP_CODE_406;
case 407: return T_HTTP_CODE_407;
case 408: return T_HTTP_CODE_408;
case 409: return T_HTTP_CODE_409;
case 410: return T_HTTP_CODE_410;
case 411: return T_HTTP_CODE_411;
case 412: return T_HTTP_CODE_412;
case 413: return T_HTTP_CODE_413;
case 414: return T_HTTP_CODE_414;
case 415: return T_HTTP_CODE_415;
case 416: return T_HTTP_CODE_416;
case 417: return T_HTTP_CODE_417;
case 429: return T_HTTP_CODE_429;
case 500: return T_HTTP_CODE_500;
case 501: return T_HTTP_CODE_501;
case 502: return T_HTTP_CODE_502;
case 503: return T_HTTP_CODE_503;
case 504: return T_HTTP_CODE_504;
case 505: return T_HTTP_CODE_505;
default: return T_HTTP_CODE_ANY;
}
}
AsyncWebServerResponse::AsyncWebServerResponse()
: _code(0), _contentType(), _contentLength(0), _sendContentLength(true), _chunked(false), _headLength(0), _sentLength(0), _ackedLength(0), _writtenLength(0), _state(RESPONSE_SETUP) {
for (const auto& header : DefaultHeaders::Instance()) {
: _code(0), _contentType(), _contentLength(0), _sendContentLength(true), _chunked(false), _headLength(0), _sentLength(0), _ackedLength(0), _writtenLength(0),
_state(RESPONSE_SETUP) {
for (const auto &header : DefaultHeaders::Instance()) {
_headers.emplace_back(header);
}
}
void AsyncWebServerResponse::setCode(int code) {
if (_state == RESPONSE_SETUP)
if (_state == RESPONSE_SETUP) {
_code = code;
}
}
void AsyncWebServerResponse::setContentLength(size_t len) {
if (_state == RESPONSE_SETUP && addHeader(T_Content_Length, len, true))
if (_state == RESPONSE_SETUP && addHeader(T_Content_Length, len, true)) {
_contentLength = len;
}
}
void AsyncWebServerResponse::setContentType(const char* type) {
if (_state == RESPONSE_SETUP && addHeader(T_Content_Type, type, true))
void AsyncWebServerResponse::setContentType(const char *type) {
if (_state == RESPONSE_SETUP && addHeader(T_Content_Type, type, true)) {
_contentType = type;
}
}
bool AsyncWebServerResponse::removeHeader(const char* name) {
for (auto i = _headers.begin(); i != _headers.end(); ++i) {
bool AsyncWebServerResponse::removeHeader(const char *name) {
bool h_erased = false;
for (auto i = _headers.begin(); i != _headers.end();) {
if (i->name().equalsIgnoreCase(name)) {
_headers.erase(i);
h_erased = true;
} else {
++i;
}
}
return h_erased;
}
bool AsyncWebServerResponse::removeHeader(const char *name, const char *value) {
for (auto i = _headers.begin(); i != _headers.end(); ++i) {
if (i->name().equalsIgnoreCase(name) && i->value().equalsIgnoreCase(value)) {
_headers.erase(i);
return true;
}
@ -158,12 +118,23 @@ bool AsyncWebServerResponse::removeHeader(const char* name) {
return false;
}
const AsyncWebHeader* AsyncWebServerResponse::getHeader(const char* name) const {
auto iter = std::find_if(std::begin(_headers), std::end(_headers), [&name](const AsyncWebHeader& header) { return header.name().equalsIgnoreCase(name); });
const AsyncWebHeader *AsyncWebServerResponse::getHeader(const char *name) const {
auto iter = std::find_if(std::begin(_headers), std::end(_headers), [&name](const AsyncWebHeader &header) {
return header.name().equalsIgnoreCase(name);
});
return (iter == std::end(_headers)) ? nullptr : &(*iter);
}
bool AsyncWebServerResponse::addHeader(const char* name, const char* value, bool replaceExisting) {
bool AsyncWebServerResponse::headerMustBePresentOnce(const String &name) {
for (uint8_t i = 0; i < T_only_once_headers_len; i++) {
if (name.equalsIgnoreCase(T_only_once_headers[i])) {
return true;
}
}
return false;
}
bool AsyncWebServerResponse::addHeader(const char *name, const char *value, bool replaceExisting) {
for (auto i = _headers.begin(); i != _headers.end(); ++i) {
if (i->name().equalsIgnoreCase(name)) {
// header already set
@ -171,9 +142,11 @@ bool AsyncWebServerResponse::addHeader(const char* name, const char* value, bool
// remove, break and add the new one
_headers.erase(i);
break;
} else {
} else if (headerMustBePresentOnce(i->name())) { // we can have only one header with that name
// do not update
return false;
} else {
break; // accept multiple headers with the same name
}
}
}
@ -182,24 +155,28 @@ bool AsyncWebServerResponse::addHeader(const char* name, const char* value, bool
return true;
}
void AsyncWebServerResponse::_assembleHead(String& buffer, uint8_t version) {
void AsyncWebServerResponse::_assembleHead(String &buffer, uint8_t version) {
if (version) {
addHeader(T_Accept_Ranges, T_none, false);
if (_chunked)
if (_chunked) {
addHeader(T_Transfer_Encoding, T_chunked, false);
}
}
if (_sendContentLength)
if (_sendContentLength) {
addHeader(T_Content_Length, String(_contentLength), false);
}
if (_contentType.length())
if (_contentType.length()) {
addHeader(T_Content_Type, _contentType.c_str(), false);
}
// precompute buffer size to avoid reallocations by String class
size_t len = 0;
len += 50; // HTTP/1.1 200 <reason>\r\n
for (const auto& header : _headers)
len += 50; // HTTP/1.1 200 <reason>\r\n
for (const auto &header : _headers) {
len += header.name().length() + header.value().length() + 4;
}
// prepare buffer
buffer.reserve(len);
@ -218,7 +195,7 @@ void AsyncWebServerResponse::_assembleHead(String& buffer, uint8_t version) {
buffer.concat(T_rn);
// Add headers
for (const auto& header : _headers) {
for (const auto &header : _headers) {
buffer.concat(header.name());
#ifdef ESP8266
buffer.concat(PSTR(": "));
@ -233,15 +210,23 @@ void AsyncWebServerResponse::_assembleHead(String& buffer, uint8_t version) {
_headLength = buffer.length();
}
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) {
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) {
size_t AsyncWebServerResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time) {
(void)request;
(void)len;
(void)time;
@ -251,19 +236,20 @@ size_t AsyncWebServerResponse::_ack(AsyncWebServerRequest* request, size_t len,
/*
* String/Code Response
* */
AsyncBasicResponse::AsyncBasicResponse(int code, const char* contentType, const char* content) {
AsyncBasicResponse::AsyncBasicResponse(int code, const char *contentType, const char *content) {
_code = code;
_content = content;
_contentType = contentType;
if (_content.length()) {
_contentLength = _content.length();
if (!_contentType.length())
if (!_contentType.length()) {
_contentType = T_text_plain;
}
}
addHeader(T_Connection, T_close, false);
}
void AsyncBasicResponse::_respond(AsyncWebServerRequest* request) {
void AsyncBasicResponse::_respond(AsyncWebServerRequest *request) {
_state = RESPONSE_HEADERS;
String out;
_assembleHead(out, request->version());
@ -298,7 +284,7 @@ void AsyncBasicResponse::_respond(AsyncWebServerRequest* request) {
}
}
size_t AsyncBasicResponse::_ack(AsyncWebServerRequest* request, size_t len, uint32_t time) {
size_t AsyncBasicResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time) {
(void)time;
_ackedLength += len;
if (_state == RESPONSE_CONTENT) {
@ -338,14 +324,14 @@ AsyncAbstractResponse::AsyncAbstractResponse(AwsTemplateProcessor callback) : _c
}
}
void AsyncAbstractResponse::_respond(AsyncWebServerRequest* request) {
void AsyncAbstractResponse::_respond(AsyncWebServerRequest *request) {
addHeader(T_Connection, T_close, false);
_assembleHead(_head, request->version());
_state = RESPONSE_HEADERS;
_ack(request, 0, 0);
}
size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest* request, size_t len, uint32_t time) {
size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time) {
(void)time;
if (!_sourceValid()) {
_state = RESPONSE_FAILED;
@ -355,14 +341,15 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest* request, size_t len, u
#if ASYNCWEBSERVER_USE_CHUNK_INFLIGHT
// return a credit for each chunk of acked data (polls does not give any credits)
if (len)
if (len) {
++_in_flight_credit;
}
// for chunked responses ignore acks if there are no _in_flight_credits left
if (_chunked && !_in_flight_credit) {
#ifdef ESP32
#ifdef ESP32
log_d("(chunk) out of in-flight credits");
#endif
#endif
return 0;
}
@ -384,7 +371,7 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest* request, size_t len, u
_writtenLength += request->client()->write(out.c_str(), out.length());
#if ASYNCWEBSERVER_USE_CHUNK_INFLIGHT
_in_flight += out.length();
--_in_flight_credit; // take a credit
--_in_flight_credit; // take a credit
#endif
return out.length();
}
@ -399,8 +386,9 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest* request, size_t len, u
if (_in_flight > space) {
// log_d("defer user call %u/%u", _in_flight, space);
// take the credit back since we are ignoring this ack and rely on other inflight data
if (len)
if (len) {
--_in_flight_credit;
}
return 0;
}
#endif
@ -418,9 +406,12 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest* request, size_t len, u
outLen = ((_contentLength - _sentLength) > space) ? space : (_contentLength - _sentLength);
}
uint8_t* buf = (uint8_t*)malloc(outLen + headLen);
uint8_t *buf = (uint8_t *)malloc(outLen + headLen);
if (!buf) {
// os_printf("_ack malloc %d failed\n", outLen+headLen);
#ifdef ESP32
log_e("Failed to allocate");
#endif
request->abort();
return 0;
}
@ -438,7 +429,7 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest* request, size_t len, u
free(buf);
return 0;
}
outLen = sprintf((char*)buf + headLen, "%04x", readLen) + headLen;
outLen = sprintf((char *)buf + headLen, "%04x", readLen) + headLen;
buf[outLen++] = '\r';
buf[outLen++] = '\n';
outLen += readLen;
@ -458,10 +449,10 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest* request, size_t len, u
}
if (outLen) {
_writtenLength += request->client()->write((const char*)buf, outLen);
_writtenLength += request->client()->write((const char *)buf, outLen);
#if ASYNCWEBSERVER_USE_CHUNK_INFLIGHT
_in_flight += outLen;
--_in_flight_credit; // take a credit
--_in_flight_credit; // take a credit
#endif
}
@ -481,14 +472,15 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest* request, size_t len, u
} else if (_state == RESPONSE_WAIT_ACK) {
if (!_sendContentLength || _ackedLength >= _writtenLength) {
_state = RESPONSE_END;
if (!_chunked && !_sendContentLength)
if (!_chunked && !_sendContentLength) {
request->client()->close(true);
}
}
}
return 0;
}
size_t AsyncAbstractResponse::_readDataFromCacheOrContent(uint8_t* data, const size_t len) {
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) {
@ -501,17 +493,20 @@ size_t AsyncAbstractResponse::_readDataFromCacheOrContent(uint8_t* data, const s
return readFromCache + readFromContent;
}
size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size_t len) {
if (!_callback)
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;
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;
@ -522,35 +517,39 @@ size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size
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.
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
} 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));
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);
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));
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
} 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
} 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
}
} 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.
@ -558,7 +557,7 @@ size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size
// 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 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
@ -567,27 +566,28 @@ size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size
_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)
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...
} 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
} 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)
} // while(pTemplateStart)
return len;
}
@ -595,64 +595,66 @@ size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size
* File Response
* */
void AsyncFileResponse::_setContentTypeFromPath(const String& path) {
void AsyncFileResponse::_setContentTypeFromPath(const String &path) {
#if HAVE_EXTERN_GET_Content_Type_FUNCTION
#ifndef ESP8266
extern const char* getContentType(const String& path);
#else
extern const __FlashStringHelper* getContentType(const String& path);
#endif
#ifndef ESP8266
extern const char *getContentType(const String &path);
#else
extern const __FlashStringHelper *getContentType(const String &path);
#endif
_contentType = getContentType(path);
#else
if (path.endsWith(T__html))
if (path.endsWith(T__html)) {
_contentType = T_text_html;
else if (path.endsWith(T__htm))
} else if (path.endsWith(T__htm)) {
_contentType = T_text_html;
else if (path.endsWith(T__css))
} else if (path.endsWith(T__css)) {
_contentType = T_text_css;
else if (path.endsWith(T__json))
} else if (path.endsWith(T__json)) {
_contentType = T_application_json;
else if (path.endsWith(T__js))
} else if (path.endsWith(T__js)) {
_contentType = T_application_javascript;
else if (path.endsWith(T__png))
} else if (path.endsWith(T__png)) {
_contentType = T_image_png;
else if (path.endsWith(T__gif))
} else if (path.endsWith(T__gif)) {
_contentType = T_image_gif;
else if (path.endsWith(T__jpg))
} else if (path.endsWith(T__jpg)) {
_contentType = T_image_jpeg;
else if (path.endsWith(T__ico))
} else if (path.endsWith(T__ico)) {
_contentType = T_image_x_icon;
else if (path.endsWith(T__svg))
} else if (path.endsWith(T__svg)) {
_contentType = T_image_svg_xml;
else if (path.endsWith(T__eot))
} else if (path.endsWith(T__eot)) {
_contentType = T_font_eot;
else if (path.endsWith(T__woff))
} else if (path.endsWith(T__woff)) {
_contentType = T_font_woff;
else if (path.endsWith(T__woff2))
} else if (path.endsWith(T__woff2)) {
_contentType = T_font_woff2;
else if (path.endsWith(T__ttf))
} else if (path.endsWith(T__ttf)) {
_contentType = T_font_ttf;
else if (path.endsWith(T__xml))
} else if (path.endsWith(T__xml)) {
_contentType = T_text_xml;
else if (path.endsWith(T__pdf))
} else if (path.endsWith(T__pdf)) {
_contentType = T_application_pdf;
else if (path.endsWith(T__zip))
} else if (path.endsWith(T__zip)) {
_contentType = T_application_zip;
else if (path.endsWith(T__gz))
} else if (path.endsWith(T__gz)) {
_contentType = T_application_x_gzip;
else
} else {
_contentType = T_text_plain;
}
#endif
}
AsyncFileResponse::AsyncFileResponse(FS& fs, const String& path, const char* contentType, bool download, AwsTemplateProcessor callback) : AsyncAbstractResponse(callback) {
AsyncFileResponse::AsyncFileResponse(FS &fs, const String &path, const char *contentType, bool download, AwsTemplateProcessor callback)
: AsyncAbstractResponse(callback) {
_code = 200;
_path = path;
if (!download && !fs.exists(_path) && fs.exists(_path + T__gz)) {
_path = _path + T__gz;
addHeader(T_Content_Encoding, T_gzip, false);
_callback = nullptr; // Unable to process zipped templates
_callback = nullptr; // Unable to process zipped templates
_sendContentLength = true;
_chunked = false;
}
@ -660,14 +662,15 @@ AsyncFileResponse::AsyncFileResponse(FS& fs, const String& path, const char* con
_content = fs.open(_path, fs::FileOpenMode::read);
_contentLength = _content.size();
if (strlen(contentType) == 0)
if (strlen(contentType) == 0) {
_setContentTypeFromPath(path);
else
} else {
_contentType = contentType;
}
int filenameStart = path.lastIndexOf('/') + 1;
char buf[26 + path.length() - filenameStart];
char* filename = (char*)path.c_str() + filenameStart;
char *filename = (char *)path.c_str() + filenameStart;
if (download) {
// set filename and force download
@ -679,13 +682,14 @@ AsyncFileResponse::AsyncFileResponse(FS& fs, const String& path, const char* con
addHeader(T_Content_Disposition, buf, false);
}
AsyncFileResponse::AsyncFileResponse(File content, const String& path, const char* contentType, bool download, AwsTemplateProcessor callback) : AsyncAbstractResponse(callback) {
AsyncFileResponse::AsyncFileResponse(File content, const String &path, const char *contentType, bool download, AwsTemplateProcessor callback)
: AsyncAbstractResponse(callback) {
_code = 200;
_path = path;
if (!download && String(content.name()).endsWith(T__gz) && !path.endsWith(T__gz)) {
addHeader(T_Content_Encoding, T_gzip, false);
_callback = nullptr; // Unable to process gzipped templates
_callback = nullptr; // Unable to process gzipped templates
_sendContentLength = true;
_chunked = false;
}
@ -693,14 +697,15 @@ AsyncFileResponse::AsyncFileResponse(File content, const String& path, const cha
_content = content;
_contentLength = _content.size();
if (strlen(contentType) == 0)
if (strlen(contentType) == 0) {
_setContentTypeFromPath(path);
else
} else {
_contentType = contentType;
}
int filenameStart = path.lastIndexOf('/') + 1;
char buf[26 + path.length() - filenameStart];
char* filename = (char*)path.c_str() + filenameStart;
char *filename = (char *)path.c_str() + filenameStart;
if (download) {
snprintf_P(buf, sizeof(buf), PSTR("attachment; filename=\"%s\""), filename);
@ -710,7 +715,7 @@ AsyncFileResponse::AsyncFileResponse(File content, const String& path, const cha
addHeader(T_Content_Disposition, buf, false);
}
size_t AsyncFileResponse::_fillBuffer(uint8_t* data, size_t len) {
size_t AsyncFileResponse::_fillBuffer(uint8_t *data, size_t len) {
return _content.read(data, len);
}
@ -718,19 +723,20 @@ size_t AsyncFileResponse::_fillBuffer(uint8_t* data, size_t len) {
* Stream Response
* */
AsyncStreamResponse::AsyncStreamResponse(Stream& stream, const char* contentType, size_t len, AwsTemplateProcessor callback) : AsyncAbstractResponse(callback) {
AsyncStreamResponse::AsyncStreamResponse(Stream &stream, const char *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 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++)
for (i = 0; i < outLen; i++) {
data[i] = _content->read();
}
return outLen;
}
@ -738,17 +744,19 @@ size_t AsyncStreamResponse::_fillBuffer(uint8_t* data, size_t len) {
* Callback Response
* */
AsyncCallbackResponse::AsyncCallbackResponse(const char* contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback) : AsyncAbstractResponse(templateCallback) {
AsyncCallbackResponse::AsyncCallbackResponse(const char *contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback)
: AsyncAbstractResponse(templateCallback) {
_code = 200;
_content = callback;
_contentLength = len;
if (!len)
if (!len) {
_sendContentLength = false;
}
_contentType = contentType;
_filledLength = 0;
}
size_t AsyncCallbackResponse::_fillBuffer(uint8_t* data, size_t len) {
size_t AsyncCallbackResponse::_fillBuffer(uint8_t *data, size_t len) {
size_t ret = _content(data, len, _filledLength);
if (ret != RESPONSE_TRY_AGAIN) {
_filledLength += ret;
@ -760,7 +768,8 @@ size_t AsyncCallbackResponse::_fillBuffer(uint8_t* data, size_t len) {
* Chunked Response
* */
AsyncChunkedResponse::AsyncChunkedResponse(const char* contentType, AwsResponseFiller callback, AwsTemplateProcessor processorCallback) : AsyncAbstractResponse(processorCallback) {
AsyncChunkedResponse::AsyncChunkedResponse(const char *contentType, AwsResponseFiller callback, AwsTemplateProcessor processorCallback)
: AsyncAbstractResponse(processorCallback) {
_code = 200;
_content = callback;
_contentLength = 0;
@ -770,7 +779,7 @@ AsyncChunkedResponse::AsyncChunkedResponse(const char* contentType, AwsResponseF
_filledLength = 0;
}
size_t AsyncChunkedResponse::_fillBuffer(uint8_t* data, size_t len) {
size_t AsyncChunkedResponse::_fillBuffer(uint8_t *data, size_t len) {
size_t ret = _content(data, len, _filledLength);
if (ret != RESPONSE_TRY_AGAIN) {
_filledLength += ret;
@ -782,7 +791,8 @@ size_t AsyncChunkedResponse::_fillBuffer(uint8_t* data, size_t len) {
* Progmem Response
* */
AsyncProgmemResponse::AsyncProgmemResponse(int code, const char* contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback) : AsyncAbstractResponse(callback) {
AsyncProgmemResponse::AsyncProgmemResponse(int code, const char *contentType, const uint8_t *content, size_t len, AwsTemplateProcessor callback)
: AsyncAbstractResponse(callback) {
_code = code;
_content = content;
_contentType = contentType;
@ -790,7 +800,7 @@ AsyncProgmemResponse::AsyncProgmemResponse(int code, const char* contentType, co
_readLength = 0;
}
size_t AsyncProgmemResponse::_fillBuffer(uint8_t* data, size_t len) {
size_t AsyncProgmemResponse::_fillBuffer(uint8_t *data, size_t len) {
size_t left = _contentLength - _readLength;
if (left > len) {
memcpy_P(data, _content + _readLength, len);
@ -806,20 +816,25 @@ size_t AsyncProgmemResponse::_fillBuffer(uint8_t* data, size_t len) {
* Response Stream (You can print/write/printf to it, up to the contentLen bytes)
* */
AsyncResponseStream::AsyncResponseStream(const char* contentType, size_t bufferSize) {
AsyncResponseStream::AsyncResponseStream(const char *contentType, size_t bufferSize) {
_code = 200;
_contentLength = 0;
_contentType = contentType;
_content.reserve(bufferSize);
if (!_content.reserve(bufferSize)) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
}
}
size_t AsyncResponseStream::_fillBuffer(uint8_t* buf, size_t maxLen) {
return _content.readBytes((char*)buf, maxLen);
size_t AsyncResponseStream::_fillBuffer(uint8_t *buf, size_t maxLen) {
return _content.readBytes((char *)buf, maxLen);
}
size_t AsyncResponseStream::write(const uint8_t* data, size_t len) {
if (_started())
size_t AsyncResponseStream::write(const uint8_t *data, size_t len) {
if (_started()) {
return 0;
}
size_t written = _content.write(data, len);
_contentLength += written;
return written;

View file

@ -1,29 +1,12 @@
/*
Asynchronous WebServer library for Espressif MCUs
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
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"
using namespace asyncsrv;
bool ON_STA_FILTER(AsyncWebServerRequest* request) {
bool ON_STA_FILTER(AsyncWebServerRequest *request) {
#ifndef CONFIG_IDF_TARGET_ESP32H2
return WiFi.localIP() == request->client()->localIP();
#else
@ -31,7 +14,7 @@ bool ON_STA_FILTER(AsyncWebServerRequest* request) {
#endif
}
bool ON_AP_FILTER(AsyncWebServerRequest* request) {
bool ON_AP_FILTER(AsyncWebServerRequest *request) {
#ifndef CONFIG_IDF_TARGET_ESP32H2
return WiFi.localIP() != request->client()->localIP();
#else
@ -40,51 +23,51 @@ bool ON_AP_FILTER(AsyncWebServerRequest* request) {
}
#ifndef HAVE_FS_FILE_OPEN_MODE
const char* fs::FileOpenMode::read = "r";
const char* fs::FileOpenMode::write = "w";
const char* fs::FileOpenMode::append = "a";
const char *fs::FileOpenMode::read = "r";
const char *fs::FileOpenMode::write = "w";
const char *fs::FileOpenMode::append = "a";
#endif
AsyncWebServer::AsyncWebServer(uint16_t port)
: _server(port) {
AsyncWebServer::AsyncWebServer(uint16_t port) : _server(port) {
_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->abort();
delete c;
}
},
this);
_server.onClient(
[](void *s, AsyncClient *c) {
if (c == NULL) {
return;
}
c->setRxTimeout(3);
AsyncWebServerRequest *r = new AsyncWebServerRequest((AsyncWebServer *)s, c);
if (r == NULL) {
c->abort();
delete c;
}
},
this
);
}
AsyncWebServer::~AsyncWebServer() {
reset();
end();
if (_catchAllHandler)
delete _catchAllHandler;
delete _catchAllHandler;
_catchAllHandler = nullptr; // Prevent potential use-after-free
}
AsyncWebRewrite& AsyncWebServer::addRewrite(std::shared_ptr<AsyncWebRewrite> rewrite) {
AsyncWebRewrite &AsyncWebServer::addRewrite(std::shared_ptr<AsyncWebRewrite> rewrite) {
_rewrites.emplace_back(rewrite);
return *_rewrites.back().get();
}
AsyncWebRewrite& AsyncWebServer::addRewrite(AsyncWebRewrite* rewrite) {
AsyncWebRewrite &AsyncWebServer::addRewrite(AsyncWebRewrite *rewrite) {
_rewrites.emplace_back(rewrite);
return *_rewrites.back().get();
}
bool AsyncWebServer::removeRewrite(AsyncWebRewrite* rewrite) {
bool AsyncWebServer::removeRewrite(AsyncWebRewrite *rewrite) {
return removeRewrite(rewrite->from().c_str(), rewrite->toUrl().c_str());
}
bool AsyncWebServer::removeRewrite(const char* from, const char* to) {
bool AsyncWebServer::removeRewrite(const char *from, const char *to) {
for (auto r = _rewrites.begin(); r != _rewrites.end(); ++r) {
if (r->get()->from() == from && r->get()->toUrl() == to) {
_rewrites.erase(r);
@ -94,17 +77,17 @@ bool AsyncWebServer::removeRewrite(const char* from, const char* to) {
return false;
}
AsyncWebRewrite& AsyncWebServer::rewrite(const char* from, const char* to) {
AsyncWebRewrite &AsyncWebServer::rewrite(const char *from, const char *to) {
_rewrites.emplace_back(std::make_shared<AsyncWebRewrite>(from, to));
return *_rewrites.back().get();
}
AsyncWebHandler& AsyncWebServer::addHandler(AsyncWebHandler* handler) {
AsyncWebHandler &AsyncWebServer::addHandler(AsyncWebHandler *handler) {
_handlers.emplace_back(handler);
return *(_handlers.back().get());
}
bool AsyncWebServer::removeHandler(AsyncWebHandler* handler) {
bool AsyncWebServer::removeHandler(AsyncWebHandler *handler) {
for (auto i = _handlers.begin(); i != _handlers.end(); ++i) {
if (i->get() == handler) {
_handlers.erase(i);
@ -124,21 +107,23 @@ void AsyncWebServer::end() {
}
#if ASYNC_TCP_SSL_ENABLED
void AsyncWebServer::onSslFileRequest(AcSSlFileHandler cb, void* arg) {
void AsyncWebServer::onSslFileRequest(AcSSlFileHandler cb, void *arg) {
_server.onSslFileRequest(cb, arg);
}
void AsyncWebServer::beginSecure(const char* cert, const char* key, const char* password) {
void AsyncWebServer::beginSecure(const char *cert, const char *key, const char *password) {
_server.beginSecure(cert, key, password);
}
#endif
void AsyncWebServer::_handleDisconnect(AsyncWebServerRequest* request) {
void AsyncWebServer::_handleDisconnect(AsyncWebServerRequest *request) {
delete request;
}
void AsyncWebServer::_rewriteRequest(AsyncWebServerRequest* request) {
for (const auto& r : _rewrites) {
void AsyncWebServer::_rewriteRequest(AsyncWebServerRequest *request) {
// the last rewrite that matches the request will be used
// we do not break the loop to allow for multiple rewrites to be applied and only the last one to be used (allows overriding)
for (const auto &r : _rewrites) {
if (r->match(request)) {
request->_url = r->toUrl();
request->_addGetParams(r->params());
@ -146,8 +131,8 @@ void AsyncWebServer::_rewriteRequest(AsyncWebServerRequest* request) {
}
}
void AsyncWebServer::_attachHandler(AsyncWebServerRequest* request) {
for (auto& h : _handlers) {
void AsyncWebServer::_attachHandler(AsyncWebServerRequest *request) {
for (auto &h : _handlers) {
if (h->filter(request) && h->canHandle(request)) {
request->setHandler(h.get());
return;
@ -157,8 +142,10 @@ void AsyncWebServer::_attachHandler(AsyncWebServerRequest* request) {
request->setHandler(_catchAllHandler);
}
AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody) {
AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler();
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);
@ -168,8 +155,8 @@ AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodCom
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);
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;
}
@ -186,13 +173,15 @@ void AsyncWebServer::onRequestBody(ArBodyHandlerFunction fn) {
_catchAllHandler->onBody(fn);
}
AsyncWebHandler &AsyncWebServer::catchAllHandler() const {
return *_catchAllHandler;
}
void AsyncWebServer::reset() {
_rewrites.clear();
_handlers.clear();
if (_catchAllHandler != NULL) {
_catchAllHandler->onRequest(NULL);
_catchAllHandler->onUpload(NULL);
_catchAllHandler->onBody(NULL);
}
_catchAllHandler->onRequest(NULL);
_catchAllHandler->onUpload(NULL);
_catchAllHandler->onBody(NULL);
}

View file

@ -1,183 +1,193 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
#pragma once
namespace asyncsrv {
static constexpr const char* empty = "";
static constexpr const char *empty = "";
static constexpr const char* T__opaque = "\", opaque=\"";
static constexpr const char* T_100_CONTINUE = "100-continue";
static constexpr const char* T_13 = "13";
static constexpr const char* T_ACCEPT = "accept";
static constexpr const char* T_Accept_Ranges = "accept-ranges";
static constexpr const char* T_app_xform_urlencoded = "application/x-www-form-urlencoded";
static constexpr const char* T_AUTH = "authorization";
static constexpr const char* T_auth_nonce = "\", qop=\"auth\", nonce=\"";
static constexpr const char* T_BASIC = "basic";
static constexpr const char* T_BASIC_REALM = "basic realm=\"";
static constexpr const char* T_BEARER = "bearer";
static constexpr const char* T_BODY = "body";
static constexpr const char* T_Cache_Control = "cache-control";
static constexpr const char* T_chunked = "chunked";
static constexpr const char* T_close = "close";
static constexpr const char* T_cnonce = "cnonce";
static constexpr const char* T_Connection = "connection";
static constexpr const char* T_Content_Disposition = "content-disposition";
static constexpr const char* T_Content_Encoding = "content-encoding";
static constexpr const char* T_Content_Length = "content-length";
static constexpr const char* T_Content_Type = "content-type";
static constexpr const char* T_Cookie = "cookie";
static constexpr const char* T_CORS_ACAC = "access-control-allow-credentials";
static constexpr const char* T_CORS_ACAH = "access-control-allow-headers";
static constexpr const char* T_CORS_ACAM = "access-control-allow-methods";
static constexpr const char* T_CORS_ACAO = "access-control-allow-origin";
static constexpr const char* T_CORS_ACMA = "access-control-max-age";
static constexpr const char* T_CORS_O = "origin";
static constexpr const char* T_data_ = "data: ";
static constexpr const char* T_DIGEST = "digest";
static constexpr const char* T_DIGEST_ = "digest ";
static constexpr const char* T_ETag = "etag";
static constexpr const char* T_event_ = "event: ";
static constexpr const char* T_EXPECT = "expect";
static constexpr const char* T_FALSE = "false";
static constexpr const char* T_filename = "filename";
static constexpr const char* T_gzip = "gzip";
static constexpr const char* T_Host = "host";
static constexpr const char* T_HTTP_1_0 = "HTTP/1.0";
static constexpr const char* T_HTTP_100_CONT = "HTTP/1.1 100 Continue\r\n\r\n";
static constexpr const char* T_id__ = "id: ";
static constexpr const char* T_IMS = "if-modified-since";
static constexpr const char* T_INM = "if-none-match";
static constexpr const char* T_keep_alive = "keep-alive";
static constexpr const char* T_Last_Event_ID = "last-event-id";
static constexpr const char* T_Last_Modified = "last-modified";
static constexpr const char* T_LOCATION = "location";
static constexpr const char* T_LOGIN_REQ = "Login Required";
static constexpr const char* T_MULTIPART_ = "multipart/";
static constexpr const char* T_name = "name";
static constexpr const char* T_nc = "nc";
static constexpr const char* T_no_cache = "no-cache";
static constexpr const char* T_nonce = "nonce";
static constexpr const char* T_none = "none";
static constexpr const char* T_opaque = "opaque";
static constexpr const char* T_qop = "qop";
static constexpr const char* T_realm = "realm";
static constexpr const char* T_realm__ = "realm=\"";
static constexpr const char* T_response = "response";
static constexpr const char* T_retry_ = "retry: ";
static constexpr const char* T_retry_after = "retry-after";
static constexpr const char* T_nn = "\n\n";
static constexpr const char* T_rn = "\r\n";
static constexpr const char* T_rnrn = "\r\n\r\n";
static constexpr const char* T_Transfer_Encoding = "transfer-encoding";
static constexpr const char* T_TRUE = "true";
static constexpr const char* T_UPGRADE = "upgrade";
static constexpr const char* T_uri = "uri";
static constexpr const char* T_username = "username";
static constexpr const char* T_WS = "websocket";
static constexpr const char* T_WWW_AUTH = "www-authenticate";
static constexpr const char *T__opaque = "\", opaque=\"";
static constexpr const char *T_100_CONTINUE = "100-continue";
static constexpr const char *T_13 = "13";
static constexpr const char *T_ACCEPT = "accept";
static constexpr const char *T_Accept_Ranges = "accept-ranges";
static constexpr const char *T_app_xform_urlencoded = "application/x-www-form-urlencoded";
static constexpr const char *T_AUTH = "authorization";
static constexpr const char *T_auth_nonce = "\", qop=\"auth\", nonce=\"";
static constexpr const char *T_BASIC = "basic";
static constexpr const char *T_BASIC_REALM = "basic realm=\"";
static constexpr const char *T_BEARER = "bearer";
static constexpr const char *T_BODY = "body";
static constexpr const char *T_Cache_Control = "cache-control";
static constexpr const char *T_chunked = "chunked";
static constexpr const char *T_close = "close";
static constexpr const char *T_cnonce = "cnonce";
static constexpr const char *T_Connection = "connection";
static constexpr const char *T_Content_Disposition = "content-disposition";
static constexpr const char *T_Content_Encoding = "content-encoding";
static constexpr const char *T_Content_Length = "content-length";
static constexpr const char *T_Content_Type = "content-type";
static constexpr const char *T_Content_Location = "content-location";
static constexpr const char *T_Cookie = "cookie";
static constexpr const char *T_CORS_ACAC = "access-control-allow-credentials";
static constexpr const char *T_CORS_ACAH = "access-control-allow-headers";
static constexpr const char *T_CORS_ACAM = "access-control-allow-methods";
static constexpr const char *T_CORS_ACAO = "access-control-allow-origin";
static constexpr const char *T_CORS_ACMA = "access-control-max-age";
static constexpr const char *T_CORS_O = "origin";
static constexpr const char *T_data_ = "data: ";
static constexpr const char *T_Date = "date";
static constexpr const char *T_DIGEST = "digest";
static constexpr const char *T_DIGEST_ = "digest ";
static constexpr const char *T_ETag = "etag";
static constexpr const char *T_event_ = "event: ";
static constexpr const char *T_EXPECT = "expect";
static constexpr const char *T_FALSE = "false";
static constexpr const char *T_filename = "filename";
static constexpr const char *T_gzip = "gzip";
static constexpr const char *T_Host = "host";
static constexpr const char *T_HTTP_1_0 = "HTTP/1.0";
static constexpr const char *T_HTTP_100_CONT = "HTTP/1.1 100 Continue\r\n\r\n";
static constexpr const char *T_id__ = "id: ";
static constexpr const char *T_IMS = "if-modified-since";
static constexpr const char *T_INM = "if-none-match";
static constexpr const char *T_keep_alive = "keep-alive";
static constexpr const char *T_Last_Event_ID = "last-event-id";
static constexpr const char *T_Last_Modified = "last-modified";
static constexpr const char *T_LOCATION = "location";
static constexpr const char *T_LOGIN_REQ = "Login Required";
static constexpr const char *T_MULTIPART_ = "multipart/";
static constexpr const char *T_name = "name";
static constexpr const char *T_nc = "nc";
static constexpr const char *T_no_cache = "no-cache";
static constexpr const char *T_nonce = "nonce";
static constexpr const char *T_none = "none";
static constexpr const char *T_opaque = "opaque";
static constexpr const char *T_qop = "qop";
static constexpr const char *T_realm = "realm";
static constexpr const char *T_realm__ = "realm=\"";
static constexpr const char *T_response = "response";
static constexpr const char *T_retry_ = "retry: ";
static constexpr const char *T_retry_after = "retry-after";
static constexpr const char *T_nn = "\n\n";
static constexpr const char *T_rn = "\r\n";
static constexpr const char *T_rnrn = "\r\n\r\n";
static constexpr const char *T_Server = "server";
static constexpr const char *T_Transfer_Encoding = "transfer-encoding";
static constexpr const char *T_TRUE = "true";
static constexpr const char *T_UPGRADE = "upgrade";
static constexpr const char *T_uri = "uri";
static constexpr const char *T_username = "username";
static constexpr const char *T_WS = "websocket";
static constexpr const char *T_WWW_AUTH = "www-authenticate";
// HTTP Methods
// HTTP Methods
static constexpr const char* T_ANY = "ANY";
static constexpr const char* T_GET = "GET";
static constexpr const char* T_POST = "POST";
static constexpr const char* T_PUT = "PUT";
static constexpr const char* T_DELETE = "DELETE";
static constexpr const char* T_PATCH = "PATCH";
static constexpr const char* T_HEAD = "HEAD";
static constexpr const char* T_OPTIONS = "OPTIONS";
static constexpr const char* T_UNKNOWN = "UNKNOWN";
static constexpr const char *T_ANY = "ANY";
static constexpr const char *T_GET = "GET";
static constexpr const char *T_POST = "POST";
static constexpr const char *T_PUT = "PUT";
static constexpr const char *T_DELETE = "DELETE";
static constexpr const char *T_PATCH = "PATCH";
static constexpr const char *T_HEAD = "HEAD";
static constexpr const char *T_OPTIONS = "OPTIONS";
static constexpr const char *T_UNKNOWN = "UNKNOWN";
// Req content types
static constexpr const char* T_RCT_NOT_USED = "RCT_NOT_USED";
static constexpr const char* T_RCT_DEFAULT = "RCT_DEFAULT";
static constexpr const char* T_RCT_HTTP = "RCT_HTTP";
static constexpr const char* T_RCT_WS = "RCT_WS";
static constexpr const char* T_RCT_EVENT = "RCT_EVENT";
static constexpr const char* T_ERROR = "ERROR";
// Req content types
static constexpr const char *T_RCT_NOT_USED = "RCT_NOT_USED";
static constexpr const char *T_RCT_DEFAULT = "RCT_DEFAULT";
static constexpr const char *T_RCT_HTTP = "RCT_HTTP";
static constexpr const char *T_RCT_WS = "RCT_WS";
static constexpr const char *T_RCT_EVENT = "RCT_EVENT";
static constexpr const char *T_ERROR = "ERROR";
// extentions & MIME-Types
static constexpr const char* T__css = ".css";
static constexpr const char* T__eot = ".eot";
static constexpr const char* T__gif = ".gif";
static constexpr const char* T__gz = ".gz";
static constexpr const char* T__htm = ".htm";
static constexpr const char* T__html = ".html";
static constexpr const char* T__ico = ".ico";
static constexpr const char* T__jpg = ".jpg";
static constexpr const char* T__js = ".js";
static constexpr const char* T__json = ".json";
static constexpr const char* T__pdf = ".pdf";
static constexpr const char* T__png = ".png";
static constexpr const char* T__svg = ".svg";
static constexpr const char* T__ttf = ".ttf";
static constexpr const char* T__woff = ".woff";
static constexpr const char* T__woff2 = ".woff2";
static constexpr const char* T__xml = ".xml";
static constexpr const char* T__zip = ".zip";
static constexpr const char* T_application_javascript = "application/javascript";
static constexpr const char* T_application_json = "application/json";
static constexpr const char* T_application_msgpack = "application/msgpack";
static constexpr const char* T_application_pdf = "application/pdf";
static constexpr const char* T_application_x_gzip = "application/x-gzip";
static constexpr const char* T_application_zip = "application/zip";
static constexpr const char* T_font_eot = "font/eot";
static constexpr const char* T_font_ttf = "font/ttf";
static constexpr const char* T_font_woff = "font/woff";
static constexpr const char* T_font_woff2 = "font/woff2";
static constexpr const char* T_image_gif = "image/gif";
static constexpr const char* T_image_jpeg = "image/jpeg";
static constexpr const char* T_image_png = "image/png";
static constexpr const char* T_image_svg_xml = "image/svg+xml";
static constexpr const char* T_image_x_icon = "image/x-icon";
static constexpr const char* T_text_css = "text/css";
static constexpr const char* T_text_event_stream = "text/event-stream";
static constexpr const char* T_text_html = "text/html";
static constexpr const char* T_text_plain = "text/plain";
static constexpr const char* T_text_xml = "text/xml";
// extensions & MIME-Types
static constexpr const char *T__css = ".css";
static constexpr const char *T__eot = ".eot";
static constexpr const char *T__gif = ".gif";
static constexpr const char *T__gz = ".gz";
static constexpr const char *T__htm = ".htm";
static constexpr const char *T__html = ".html";
static constexpr const char *T__ico = ".ico";
static constexpr const char *T__jpg = ".jpg";
static constexpr const char *T__js = ".js";
static constexpr const char *T__json = ".json";
static constexpr const char *T__pdf = ".pdf";
static constexpr const char *T__png = ".png";
static constexpr const char *T__svg = ".svg";
static constexpr const char *T__ttf = ".ttf";
static constexpr const char *T__woff = ".woff";
static constexpr const char *T__woff2 = ".woff2";
static constexpr const char *T__xml = ".xml";
static constexpr const char *T__zip = ".zip";
static constexpr const char *T_application_javascript = "application/javascript";
static constexpr const char *T_application_json = "application/json";
static constexpr const char *T_application_msgpack = "application/msgpack";
static constexpr const char *T_application_pdf = "application/pdf";
static constexpr const char *T_application_x_gzip = "application/x-gzip";
static constexpr const char *T_application_zip = "application/zip";
static constexpr const char *T_font_eot = "font/eot";
static constexpr const char *T_font_ttf = "font/ttf";
static constexpr const char *T_font_woff = "font/woff";
static constexpr const char *T_font_woff2 = "font/woff2";
static constexpr const char *T_image_gif = "image/gif";
static constexpr const char *T_image_jpeg = "image/jpeg";
static constexpr const char *T_image_png = "image/png";
static constexpr const char *T_image_svg_xml = "image/svg+xml";
static constexpr const char *T_image_x_icon = "image/x-icon";
static constexpr const char *T_text_css = "text/css";
static constexpr const char *T_text_event_stream = "text/event-stream";
static constexpr const char *T_text_html = "text/html";
static constexpr const char *T_text_plain = "text/plain";
static constexpr const char *T_text_xml = "text/xml";
// Responce codes
static constexpr const char* T_HTTP_CODE_100 = "Continue";
static constexpr const char* T_HTTP_CODE_101 = "Switching Protocols";
static constexpr const char* T_HTTP_CODE_200 = "OK";
static constexpr const char* T_HTTP_CODE_201 = "Created";
static constexpr const char* T_HTTP_CODE_202 = "Accepted";
static constexpr const char* T_HTTP_CODE_203 = "Non-Authoritative Information";
static constexpr const char* T_HTTP_CODE_204 = "No Content";
static constexpr const char* T_HTTP_CODE_205 = "Reset Content";
static constexpr const char* T_HTTP_CODE_206 = "Partial Content";
static constexpr const char* T_HTTP_CODE_300 = "Multiple Choices";
static constexpr const char* T_HTTP_CODE_301 = "Moved Permanently";
static constexpr const char* T_HTTP_CODE_302 = "Found";
static constexpr const char* T_HTTP_CODE_303 = "See Other";
static constexpr const char* T_HTTP_CODE_304 = "Not Modified";
static constexpr const char* T_HTTP_CODE_305 = "Use Proxy";
static constexpr const char* T_HTTP_CODE_307 = "Temporary Redirect";
static constexpr const char* T_HTTP_CODE_400 = "Bad Request";
static constexpr const char* T_HTTP_CODE_401 = "Unauthorized";
static constexpr const char* T_HTTP_CODE_402 = "Payment Required";
static constexpr const char* T_HTTP_CODE_403 = "Forbidden";
static constexpr const char* T_HTTP_CODE_404 = "Not Found";
static constexpr const char* T_HTTP_CODE_405 = "Method Not Allowed";
static constexpr const char* T_HTTP_CODE_406 = "Not Acceptable";
static constexpr const char* T_HTTP_CODE_407 = "Proxy Authentication Required";
static constexpr const char* T_HTTP_CODE_408 = "Request Time-out";
static constexpr const char* T_HTTP_CODE_409 = "Conflict";
static constexpr const char* T_HTTP_CODE_410 = "Gone";
static constexpr const char* T_HTTP_CODE_411 = "Length Required";
static constexpr const char* T_HTTP_CODE_412 = "Precondition Failed";
static constexpr const char* T_HTTP_CODE_413 = "Request Entity Too Large";
static constexpr const char* T_HTTP_CODE_414 = "Request-URI Too Large";
static constexpr const char* T_HTTP_CODE_415 = "Unsupported Media Type";
static constexpr const char* T_HTTP_CODE_416 = "Requested range not satisfiable";
static constexpr const char* T_HTTP_CODE_417 = "Expectation Failed";
static constexpr const char* T_HTTP_CODE_429 = "Too Many Requests";
static constexpr const char* T_HTTP_CODE_500 = "Internal Server Error";
static constexpr const char* T_HTTP_CODE_501 = "Not Implemented";
static constexpr const char* T_HTTP_CODE_502 = "Bad Gateway";
static constexpr const char* T_HTTP_CODE_503 = "Service Unavailable";
static constexpr const char* T_HTTP_CODE_504 = "Gateway Time-out";
static constexpr const char* T_HTTP_CODE_505 = "HTTP Version not supported";
static constexpr const char* T_HTTP_CODE_ANY = "Unknown code";
// Response codes
static constexpr const char *T_HTTP_CODE_100 = "Continue";
static constexpr const char *T_HTTP_CODE_101 = "Switching Protocols";
static constexpr const char *T_HTTP_CODE_200 = "OK";
static constexpr const char *T_HTTP_CODE_201 = "Created";
static constexpr const char *T_HTTP_CODE_202 = "Accepted";
static constexpr const char *T_HTTP_CODE_203 = "Non-Authoritative Information";
static constexpr const char *T_HTTP_CODE_204 = "No Content";
static constexpr const char *T_HTTP_CODE_205 = "Reset Content";
static constexpr const char *T_HTTP_CODE_206 = "Partial Content";
static constexpr const char *T_HTTP_CODE_300 = "Multiple Choices";
static constexpr const char *T_HTTP_CODE_301 = "Moved Permanently";
static constexpr const char *T_HTTP_CODE_302 = "Found";
static constexpr const char *T_HTTP_CODE_303 = "See Other";
static constexpr const char *T_HTTP_CODE_304 = "Not Modified";
static constexpr const char *T_HTTP_CODE_305 = "Use Proxy";
static constexpr const char *T_HTTP_CODE_307 = "Temporary Redirect";
static constexpr const char *T_HTTP_CODE_400 = "Bad Request";
static constexpr const char *T_HTTP_CODE_401 = "Unauthorized";
static constexpr const char *T_HTTP_CODE_402 = "Payment Required";
static constexpr const char *T_HTTP_CODE_403 = "Forbidden";
static constexpr const char *T_HTTP_CODE_404 = "Not Found";
static constexpr const char *T_HTTP_CODE_405 = "Method Not Allowed";
static constexpr const char *T_HTTP_CODE_406 = "Not Acceptable";
static constexpr const char *T_HTTP_CODE_407 = "Proxy Authentication Required";
static constexpr const char *T_HTTP_CODE_408 = "Request Time-out";
static constexpr const char *T_HTTP_CODE_409 = "Conflict";
static constexpr const char *T_HTTP_CODE_410 = "Gone";
static constexpr const char *T_HTTP_CODE_411 = "Length Required";
static constexpr const char *T_HTTP_CODE_412 = "Precondition Failed";
static constexpr const char *T_HTTP_CODE_413 = "Request Entity Too Large";
static constexpr const char *T_HTTP_CODE_414 = "Request-URI Too Large";
static constexpr const char *T_HTTP_CODE_415 = "Unsupported Media Type";
static constexpr const char *T_HTTP_CODE_416 = "Requested range not satisfiable";
static constexpr const char *T_HTTP_CODE_417 = "Expectation Failed";
static constexpr const char *T_HTTP_CODE_429 = "Too Many Requests";
static constexpr const char *T_HTTP_CODE_500 = "Internal Server Error";
static constexpr const char *T_HTTP_CODE_501 = "Not Implemented";
static constexpr const char *T_HTTP_CODE_502 = "Bad Gateway";
static constexpr const char *T_HTTP_CODE_503 = "Service Unavailable";
static constexpr const char *T_HTTP_CODE_504 = "Gateway Time-out";
static constexpr const char *T_HTTP_CODE_505 = "HTTP Version not supported";
static constexpr const char *T_HTTP_CODE_ANY = "Unknown code";
} // namespace asyncsrv {}
static constexpr const uint8_t T_only_once_headers_len = 11;
static constexpr const char *T_only_once_headers[] = {T_Content_Length, T_Content_Type, T_Date, T_ETag, T_Last_Modified, T_LOCATION, T_retry_after,
T_Transfer_Encoding, T_Content_Location, T_Server, T_WWW_AUTH};
} // namespace asyncsrv

View file

@ -64,7 +64,7 @@ _____ _ _ ___ _____ _
#include "Update.h"
#include "StreamString.h"
#if ELEGANTOTA_USE_ASYNC_WEBSERVER == 1
#include "../../ESP32Async-AsyncTCP/src/AsyncTCP.h"
#include "../../mathieucarbou-AsyncTCPSock/src/AsyncTCP.h"
#include "../../ESP32Async-ESPAsyncWebServer/src/ESPAsyncWebServer.h"
#define ELEGANTOTA_WEBSERVER AsyncWebServer
#else

View file

@ -1,5 +1,5 @@
// ArduinoJson - https://arduinojson.org
// Copyright © 2014-2024, Benoit BLANCHON
// Copyright © 2014-2025, Benoit BLANCHON
// MIT License
#pragma once
@ -239,11 +239,11 @@
#define ARDUINOJSON_BIN2ALPHA_1111() P
#define ARDUINOJSON_BIN2ALPHA_(A, B, C, D) ARDUINOJSON_BIN2ALPHA_##A##B##C##D()
#define ARDUINOJSON_BIN2ALPHA(A, B, C, D) ARDUINOJSON_BIN2ALPHA_(A, B, C, D)
#define ARDUINOJSON_VERSION "7.3.0"
#define ARDUINOJSON_VERSION "7.3.1"
#define ARDUINOJSON_VERSION_MAJOR 7
#define ARDUINOJSON_VERSION_MINOR 3
#define ARDUINOJSON_VERSION_REVISION 0
#define ARDUINOJSON_VERSION_MACRO V730
#define ARDUINOJSON_VERSION_REVISION 1
#define ARDUINOJSON_VERSION_MACRO V731
#ifndef ARDUINOJSON_VERSION_NAMESPACE
# define ARDUINOJSON_VERSION_NAMESPACE \
ARDUINOJSON_CONCAT5( \
@ -1029,8 +1029,9 @@ template <typename TChar, size_t N>
struct StringAdapter<TChar[N], enable_if_t<IsChar<TChar>::value>> {
using AdaptedString = RamString;
static AdaptedString adapt(const TChar* p) {
ARDUINOJSON_ASSERT(p);
auto str = reinterpret_cast<const char*>(p);
return AdaptedString(str, str ? ::strlen(str) : 0);
return AdaptedString(str, ::strlen(str));
}
};
template <typename TChar>
@ -2243,6 +2244,7 @@ class VariantData {
#else
(void)resources; // silence warning
#endif
const char* str = nullptr;
switch (type_) {
case VariantType::Boolean:
return static_cast<T>(content_.asBoolean);
@ -2257,8 +2259,11 @@ class VariantData {
return static_cast<T>(extension->asInt64);
#endif
case VariantType::LinkedString:
str = content_.asLinkedString;
break;
case VariantType::OwnedString:
return parseNumber<T>(content_.asOwnedString->data);
str = content_.asOwnedString->data;
break;
case VariantType::Float:
return static_cast<T>(content_.asFloat);
#if ARDUINOJSON_USE_DOUBLE
@ -2266,8 +2271,10 @@ class VariantData {
return static_cast<T>(extension->asDouble);
#endif
default:
return 0;
return 0.0;
}
ARDUINOJSON_ASSERT(str != nullptr);
return parseNumber<T>(str);
}
template <typename T>
T asIntegral(const ResourceManager* resources) const {
@ -2277,6 +2284,7 @@ class VariantData {
#else
(void)resources; // silence warning
#endif
const char* str = nullptr;
switch (type_) {
case VariantType::Boolean:
return content_.asBoolean;
@ -2291,9 +2299,11 @@ class VariantData {
return convertNumber<T>(extension->asInt64);
#endif
case VariantType::LinkedString:
return parseNumber<T>(content_.asLinkedString);
str = content_.asLinkedString;
break;
case VariantType::OwnedString:
return parseNumber<T>(content_.asOwnedString->data);
str = content_.asOwnedString->data;
break;
case VariantType::Float:
return convertNumber<T>(content_.asFloat);
#if ARDUINOJSON_USE_DOUBLE
@ -2303,6 +2313,8 @@ class VariantData {
default:
return 0;
}
ARDUINOJSON_ASSERT(str != nullptr);
return parseNumber<T>(str);
}
ObjectData* asObject() {
return isObject() ? &content_.asObject : 0;

View file

@ -0,0 +1,22 @@
{
"name":"AsyncTCPSock",
"description":"Reimplementation of an Asynchronous TCP Library for ESP32, using BSD Sockets",
"keywords":"async,tcp",
"authors":
{
"name": "Alex Villacís Lasso",
"maintainer": true
},
"repository":
{
"type": "git",
"url": "https://github.com/yubox-node-org/AsyncTCPSock.git"
},
"version": "1.0.2-dev",
"license": "LGPL-3.0",
"frameworks": "arduino",
"platforms": "espressif32",
"build": {
"libCompatMode": 2
}
}

View file

@ -0,0 +1,9 @@
name=AsyncTCPSock
version=1.0.2-dev
author=avillacis
maintainer=avillacis
sentence=Reimplemented Async TCP Library for ESP32 using BSD Sockets
paragraph=This is a reimplementation of AsyncTCP (Async TCP Library for ESP32) by Me No Dev, using high-level BSD Sockets instead of the low-level packet API and a message queue.
category=Other
url=https://github.com/yubox-node-org/AsyncTCPSock
architectures=*

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,321 @@
/*
Reimplementation of an asynchronous TCP library for Espressif MCUs, using
BSD sockets.
Copyright (c) 2020 Alex Villacís Lasso.
Original AsyncTCP API 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 ASYNCTCP_H_
#define ASYNCTCP_H_
#include "../../../system_settings.h"
#include "../../../devboard/hal/hal.h"
#include "IPAddress.h"
#include "sdkconfig.h"
#include <functional>
#include <deque>
#include <list>
#if ASYNC_TCP_SSL_ENABLED
#include <ssl_client.h>
#include "AsyncTCP_TLS_Context.h"
#endif
extern "C" {
#include "lwip/err.h"
#include "lwip/sockets.h"
}
#define ASYNCTCP_VERSION "1.0.2-dev"
#define ASYNCTCP_VERSION_MAJOR 1
#define ASYNCTCP_VERSION_MINOR 2
#define ASYNCTCP_VERSION_REVISION 2
#define ASYNCTCP_FORK_mathieucarbou
//If core is not defined, then we are running in Arduino or PIO
#ifndef CONFIG_ASYNC_TCP_RUNNING_CORE
#define CONFIG_ASYNC_TCP_RUNNING_CORE WIFI_CORE
#define CONFIG_ASYNC_TCP_USE_WDT 0 //if enabled, adds between 33us and 200us per event
#endif
#ifndef CONFIG_ASYNC_TCP_STACK_SIZE
#define CONFIG_ASYNC_TCP_STACK_SIZE 16384 // 8192 * 2
#endif
#ifndef CONFIG_ASYNC_TCP_STACK
#define CONFIG_ASYNC_TCP_STACK CONFIG_ASYNC_TCP_STACK_SIZE
#endif
#ifndef CONFIG_ASYNC_TCP_PRIORITY
#define CONFIG_ASYNC_TCP_PRIORITY 3
#endif
#ifndef CONFIG_ASYNC_TCP_TASK_PRIORITY
#define CONFIG_ASYNC_TCP_TASK_PRIORITY TASK_CONNECTIVITY_PRIO
#endif
#ifndef CONFIG_ASYNC_TCP_TASK_NAME
#define CONFIG_ASYNC_TCP_TASK_NAME "asyncTcpSock"
#endif
class AsyncClient;
#ifndef CONFIG_ASYNC_TCP_MAX_ACK_TIME
#define CONFIG_ASYNC_TCP_MAX_ACK_TIME 5000
#endif
#ifndef ASYNC_MAX_ACK_TIME
#define ASYNC_MAX_ACK_TIME CONFIG_ASYNC_TCP_MAX_ACK_TIME
#endif
#define ASYNC_WRITE_FLAG_COPY 0x01 //will allocate new buffer to hold the data while sending (else will hold reference to the data given)
#define ASYNC_WRITE_FLAG_MORE 0x02 //will not send PSH flag, meaning that there should be more data to be sent before the application should react.
#define SSL_HANDSHAKE_TIMEOUT 5000 // timeout to complete SSL handshake
typedef std::function<void(void*, AsyncClient*)> AcConnectHandler;
typedef std::function<void(void*, AsyncClient*, size_t len, uint32_t time)> AcAckHandler;
typedef std::function<void(void*, AsyncClient*, int8_t error)> AcErrorHandler;
typedef std::function<void(void*, AsyncClient*, void *data, size_t len)> AcDataHandler;
//typedef std::function<void(void*, AsyncClient*, struct pbuf *pb)> AcPacketHandler;
typedef std::function<void(void*, AsyncClient*, uint32_t time)> AcTimeoutHandler;
class AsyncSocketBase
{
private:
static std::list<AsyncSocketBase *> & _getSocketBaseList(void);
protected:
int _socket = -1;
bool _selected = false;
bool _isdnsfinished = false;
uint32_t _sock_lastactivity = 0;
virtual void _sockIsReadable(void) {} // Action to take on readable socket
virtual bool _sockIsWriteable(void) { return false; } // Action to take on writable socket
virtual void _sockPoll(void) {} // Action to take on idle socket activity poll
virtual void _sockDelayedConnect(void) {} // Action to take on DNS-resolve finished
virtual bool _pendingWrite(void) { return false; } // Test if there is data pending to be written
virtual bool _isServer(void) { return false; } // Will a read from this socket result in one more client?
public:
AsyncSocketBase(void);
virtual ~AsyncSocketBase();
friend void _asynctcpsock_task(void *);
};
class AsyncClient : public AsyncSocketBase
{
public:
AsyncClient(int sockfd = -1);
~AsyncClient();
#if ASYNC_TCP_SSL_ENABLED
bool connect(IPAddress ip, uint16_t port, bool secure = false);
bool connect(const char* host, uint16_t port, bool secure = false);
void setRootCa(const char* rootca, const size_t len);
void setClientCert(const char* cli_cert, const size_t len);
void setClientKey(const char* cli_key, const size_t len);
void setPsk(const char* psk_ident, const char* psk);
#else
bool connect(IPAddress ip, uint16_t port);
bool connect(const char* host, uint16_t port);
#endif // ASYNC_TCP_SSL_ENABLED
void close(bool now = false);
int8_t abort();
bool free();
bool canSend() { return space() > 0; }
size_t space();
size_t add(const char* data, size_t size, uint8_t apiflags=ASYNC_WRITE_FLAG_COPY);//add for sending
bool send();
//write equals add()+send()
size_t write(const char* data);
size_t write(const char* data, size_t size, uint8_t apiflags=ASYNC_WRITE_FLAG_COPY); //only when canSend() == true
uint8_t state() { return _conn_state; }
bool connected();
bool freeable();//disconnected or disconnecting
uint32_t getAckTimeout();
void setAckTimeout(uint32_t timeout);//no ACK timeout for the last sent packet in milliseconds
uint32_t getRxTimeout();
void setRxTimeout(uint32_t timeout);//no RX data timeout for the connection in seconds
void setNoDelay(bool nodelay);
bool getNoDelay();
uint32_t getRemoteAddress();
uint16_t getRemotePort();
uint32_t getLocalAddress();
uint16_t getLocalPort();
//compatibility
IPAddress remoteIP();
uint16_t remotePort();
IPAddress localIP();
uint16_t localPort();
void onConnect(AcConnectHandler cb, void* arg = 0); //on successful connect
void onDisconnect(AcConnectHandler cb, void* arg = 0); //disconnected
void onAck(AcAckHandler cb, void* arg = 0); //ack received
void onError(AcErrorHandler cb, void* arg = 0); //unsuccessful connect or error
void onData(AcDataHandler cb, void* arg = 0); //data received
void onTimeout(AcTimeoutHandler cb, void* arg = 0); //ack timeout
void onPoll(AcConnectHandler cb, void* arg = 0); //every 125ms when connected
// The following functions are just for API compatibility and do nothing
size_t ack(size_t len) { return len; }
void ackLater() {}
const char * errorToString(int8_t error);
// const char * stateToString();
protected:
bool _sockIsWriteable(void);
void _sockIsReadable(void);
void _sockPoll(void);
void _sockDelayedConnect(void);
bool _pendingWrite(void);
private:
AcConnectHandler _connect_cb;
void* _connect_cb_arg;
AcConnectHandler _discard_cb;
void* _discard_cb_arg;
AcAckHandler _sent_cb;
void* _sent_cb_arg;
AcErrorHandler _error_cb;
void* _error_cb_arg;
AcDataHandler _recv_cb;
void* _recv_cb_arg;
AcTimeoutHandler _timeout_cb;
void* _timeout_cb_arg;
AcConnectHandler _poll_cb;
void* _poll_cb_arg;
uint32_t _rx_last_packet;
uint32_t _rx_since_timeout;
uint32_t _ack_timeout;
// Used on asynchronous DNS resolving scenario - I do not want to connect()
// from the LWIP thread itself.
struct ip_addr _connect_addr;
uint16_t _connect_port = 0;
//const char * _connect_dnsname = NULL;
#if ASYNC_TCP_SSL_ENABLED
size_t _root_ca_len;
char* _root_ca;
size_t _cli_cert_len;
char* _cli_cert;
size_t _cli_key_len;
char* _cli_key;
bool _secure;
bool _handshake_done;
const char* _psk_ident;
const char* _psk;
String _hostname;
AsyncTCP_TLS_Context * _sslctx;
#endif // ASYNC_TCP_SSL_ENABLED
// The following private struct represents a buffer enqueued with the add()
// method. Each of these buffers are flushed whenever the socket becomes
// writable
typedef struct {
uint8_t * data; // Pointer to data queued for write
uint32_t length; // Length of data queued for write
uint32_t written; // Length of data written to socket so far
uint32_t queued_at;// Timestamp at which this data buffer was queued
uint32_t written_at; // Timestamp at which this data buffer was completely written
int write_errno; // If != 0, errno value while writing this buffer
bool owned; // If true, we malloc'ed the data and should be freed after completely written.
// If false, app owns the memory and should ensure it remains valid until acked
} queued_writebuf;
// Internal struct used to implement sent buffer notification
typedef struct {
uint32_t length;
uint32_t delay;
} notify_writebuf;
// Queue of buffers to write to socket
SemaphoreHandle_t _write_mutex;
std::deque<queued_writebuf> _writeQueue;
bool _ack_timeout_signaled = false;
// Remaining space willing to queue for writing
uint32_t _writeSpaceRemaining;
// Simulation of connection state
uint8_t _conn_state;
void _error(int8_t err);
void _close(void);
void _removeAllCallbacks(void);
bool _flushWriteQueue(void);
void _clearWriteQueue(void);
void _collectNotifyWrittenBuffers(std::deque<notify_writebuf> &, int &);
void _notifyWrittenBuffers(std::deque<notify_writebuf> &, int);
#if ASYNC_TCP_SSL_ENABLED
int _runSSLHandshakeLoop(void);
#endif
friend void _tcpsock_dns_found(const char * name, struct ip_addr * ipaddr, void * arg);
};
#if ASYNC_TCP_SSL_ENABLED
typedef std::function<int(void* arg, const char *filename, uint8_t **buf)> AcSSlFileHandler;
#endif
class AsyncServer : public AsyncSocketBase
{
public:
AsyncServer(IPAddress addr, uint16_t port);
AsyncServer(uint16_t port);
~AsyncServer();
void onClient(AcConnectHandler cb, void* arg);
#if ASYNC_TCP_SSL_ENABLED
// Dummy, so it compiles with ESP Async WebServer library enabled.
void onSslFileRequest(AcSSlFileHandler cb, void* arg) {};
void beginSecure(const char *cert, const char *private_key_file, const char *password) {};
#endif
void begin();
void end();
void setNoDelay(bool nodelay) { _noDelay = nodelay; }
bool getNoDelay() { return _noDelay; }
uint8_t status();
protected:
uint16_t _port;
IPAddress _addr;
bool _noDelay;
AcConnectHandler _connect_cb;
void* _connect_cb_arg;
// Listening socket is readable on incoming connection
void _sockIsReadable(void);
// Mark this class as a server
bool _isServer(void) { return true; }
};
#endif /* ASYNCTCP_H_ */

View file

@ -0,0 +1,6 @@
#ifndef ASYNCTCP_SSL_H_
#define ASYNCTCP_SSL_H_
#include "AsyncTCP_SSL.hpp"
#endif /* ASYNCTCP_SSL_H_ */

View file

@ -0,0 +1,17 @@
#ifndef ASYNCTCP_SSL_HPP
#define ASYNCTCP_SSL_HPP
#ifdef ASYNC_TCP_SSL_ENABLED
#include <AsyncTCP.h>
#define AsyncSSLClient AsyncClient
#define AsyncSSLServer AsyncServer
#define ASYNC_TCP_SSL_VERSION "AsyncTCPSock SSL shim v0.0.1"
#else
#error Compatibility shim requires ASYNC_TCP_SSL_ENABLED to be defined!
#endif
#endif

View file

@ -0,0 +1,346 @@
#include <Arduino.h>
#include <esp32-hal-log.h>
#include <lwip/err.h>
#include <lwip/sockets.h>
#include <lwip/sys.h>
#include <lwip/netdb.h>
#include <mbedtls/sha256.h>
#include <mbedtls/oid.h>
#include "AsyncTCP_TLS_Context.h"
#if ASYNC_TCP_SSL_ENABLED
#if !defined(MBEDTLS_KEY_EXCHANGE__SOME__PSK_ENABLED) && !defined(MBEDTLS_KEY_EXCHANGE_SOME_PSK_ENABLED)
# warning "Please configure IDF framework to include mbedTLS -> Enable pre-shared-key ciphersuites and activate at least one cipher"
#else
static const char *pers = "esp32-tls";
static int _handle_error(int err, const char * function, int line)
{
if(err == -30848){
return err;
}
#ifdef MBEDTLS_ERROR_C
char error_buf[100];
mbedtls_strerror(err, error_buf, 100);
log_e("[%s():%d]: (%d) %s", function, line, err, error_buf);
#else
log_e("[%s():%d]: code %d", function, line, err);
#endif
return err;
}
#define handle_error(e) _handle_error(e, __FUNCTION__, __LINE__)
AsyncTCP_TLS_Context::AsyncTCP_TLS_Context(void)
{
mbedtls_ssl_init(&ssl_ctx);
mbedtls_ssl_config_init(&ssl_conf);
mbedtls_ctr_drbg_init(&drbg_ctx);
_socket = -1;
_have_ca_cert = false;
_have_client_cert = false;
_have_client_key = false;
handshake_timeout = 120000;
}
int AsyncTCP_TLS_Context::startSSLClientInsecure(int sck, const char * host_or_ip)
{
return _startSSLClient(sck, host_or_ip,
NULL, 0,
NULL, 0,
NULL, 0,
NULL, NULL,
true);
}
int AsyncTCP_TLS_Context::startSSLClient(int sck, const char * host_or_ip,
const char *pskIdent, const char *psKey)
{
return _startSSLClient(sck, host_or_ip,
NULL, 0,
NULL, 0,
NULL, 0,
pskIdent, psKey,
false);
}
int AsyncTCP_TLS_Context::startSSLClient(int sck, const char * host_or_ip,
const char *rootCABuff,
const char *cli_cert,
const char *cli_key)
{
return startSSLClient(sck, host_or_ip,
(const unsigned char *)rootCABuff, (rootCABuff != NULL) ? strlen(rootCABuff) + 1 : 0,
(const unsigned char *)cli_cert, (cli_cert != NULL) ? strlen(cli_cert) + 1 : 0,
(const unsigned char *)cli_key, (cli_key != NULL) ? strlen(cli_key) + 1 : 0);
}
int AsyncTCP_TLS_Context::startSSLClient(int sck, const char * host_or_ip,
const unsigned char *rootCABuff, const size_t rootCABuff_len,
const unsigned char *cli_cert, const size_t cli_cert_len,
const unsigned char *cli_key, const size_t cli_key_len)
{
return _startSSLClient(sck, host_or_ip,
rootCABuff, rootCABuff_len,
cli_cert, cli_cert_len,
cli_key, cli_key_len,
NULL, NULL,
false);
}
int AsyncTCP_TLS_Context::_startSSLClient(int sck, const char * host_or_ip,
const unsigned char *rootCABuff, const size_t rootCABuff_len,
const unsigned char *cli_cert, const size_t cli_cert_len,
const unsigned char *cli_key, const size_t cli_key_len,
const char *pskIdent, const char *psKey,
bool insecure)
{
int ret;
int enable = 1;
// The insecure flag will skip server certificate validation. Otherwise some
// certificate is required.
if (rootCABuff == NULL && pskIdent == NULL && psKey == NULL && !insecure) {
return -1;
}
#define ROE(x,msg) { if (((x)<0)) { log_e("LWIP Socket config of " msg " failed."); return -1; }}
// ROE(lwip_setsockopt(sck, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)),"SO_RCVTIMEO");
// ROE(lwip_setsockopt(sck, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)),"SO_SNDTIMEO");
ROE(lwip_setsockopt(sck, IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(enable)),"TCP_NODELAY");
ROE(lwip_setsockopt(sck, SOL_SOCKET, SO_KEEPALIVE, &enable, sizeof(enable)),"SO_KEEPALIVE");
log_v("Seeding the random number generator");
mbedtls_entropy_init(&entropy_ctx);
ret = mbedtls_ctr_drbg_seed(&drbg_ctx, mbedtls_entropy_func,
&entropy_ctx, (const unsigned char *) pers, strlen(pers));
if (ret < 0) {
return handle_error(ret);
}
log_v("Setting up the SSL/TLS structure...");
if ((ret = mbedtls_ssl_config_defaults(&ssl_conf,
MBEDTLS_SSL_IS_CLIENT,
MBEDTLS_SSL_TRANSPORT_STREAM,
MBEDTLS_SSL_PRESET_DEFAULT)) != 0) {
return handle_error(ret);
}
if (insecure) {
mbedtls_ssl_conf_authmode(&ssl_conf, MBEDTLS_SSL_VERIFY_NONE);
log_i("WARNING: Skipping SSL Verification. INSECURE!");
} else if (rootCABuff != NULL) {
log_v("Loading CA cert");
mbedtls_x509_crt_init(&ca_cert);
mbedtls_ssl_conf_authmode(&ssl_conf, MBEDTLS_SSL_VERIFY_REQUIRED);
ret = mbedtls_x509_crt_parse(&ca_cert, rootCABuff, rootCABuff_len);
_have_ca_cert = true;
mbedtls_ssl_conf_ca_chain(&ssl_conf, &ca_cert, NULL);
if (ret < 0) {
// free the ca_cert in the case parse failed, otherwise, the old ca_cert still in the heap memory, that lead to "out of memory" crash.
_deleteHandshakeCerts();
return handle_error(ret);
}
} else if (pskIdent != NULL && psKey != NULL) {
log_v("Setting up PSK");
// convert PSK from hex to binary
if ((strlen(psKey) & 1) != 0 || strlen(psKey) > 2*MBEDTLS_PSK_MAX_LEN) {
log_e("pre-shared key not valid hex or too long");
return -1;
}
unsigned char psk[MBEDTLS_PSK_MAX_LEN];
size_t psk_len = strlen(psKey)/2;
for (int j=0; j<strlen(psKey); j+= 2) {
char c = psKey[j];
if (c >= '0' && c <= '9') c -= '0';
else if (c >= 'A' && c <= 'F') c -= 'A' - 10;
else if (c >= 'a' && c <= 'f') c -= 'a' - 10;
else return -1;
psk[j/2] = c<<4;
c = psKey[j+1];
if (c >= '0' && c <= '9') c -= '0';
else if (c >= 'A' && c <= 'F') c -= 'A' - 10;
else if (c >= 'a' && c <= 'f') c -= 'a' - 10;
else return -1;
psk[j/2] |= c;
}
// set mbedtls config
ret = mbedtls_ssl_conf_psk(&ssl_conf, psk, psk_len,
(const unsigned char *)pskIdent, strlen(pskIdent));
if (ret != 0) {
log_e("mbedtls_ssl_conf_psk returned %d", ret);
return handle_error(ret);
}
} else {
return -1;
}
if (!insecure && cli_cert != NULL && cli_key != NULL) {
mbedtls_x509_crt_init(&client_cert);
mbedtls_pk_init(&client_key);
log_v("Loading CRT cert");
ret = mbedtls_x509_crt_parse(&client_cert, cli_cert, cli_cert_len);
_have_client_cert = true;
if (ret < 0) {
// free the client_cert in the case parse failed, otherwise, the old client_cert still in the heap memory, that lead to "out of memory" crash.
_deleteHandshakeCerts();
return handle_error(ret);
}
log_v("Loading private key");
#if MBEDTLS_VERSION_NUMBER < 0x03000000
ret = mbedtls_pk_parse_key(&client_key, cli_key, cli_key_len, NULL, 0);
#else
ret = mbedtls_pk_parse_key(&client_key, cli_key, cli_key_len, NULL, 0, mbedtls_ctr_drbg_random, &drbg_ctx);
#endif
_have_client_key = true;
if (ret != 0) {
_deleteHandshakeCerts();
return handle_error(ret);
}
mbedtls_ssl_conf_own_cert(&ssl_conf, &client_cert, &client_key);
}
log_v("Setting hostname for TLS session...");
// Hostname set here should match CN in server certificate
if ((ret = mbedtls_ssl_set_hostname(&ssl_ctx, host_or_ip)) != 0){
_deleteHandshakeCerts();
return handle_error(ret);
}
mbedtls_ssl_conf_rng(&ssl_conf, mbedtls_ctr_drbg_random, &drbg_ctx);
if ((ret = mbedtls_ssl_setup(&ssl_ctx, &ssl_conf)) != 0) {
_deleteHandshakeCerts();
return handle_error(ret);
}
_socket = sck;
mbedtls_ssl_set_bio(&ssl_ctx, &_socket, mbedtls_net_send, mbedtls_net_recv, NULL );
handshake_start_time = 0;
return 0;
}
int AsyncTCP_TLS_Context::runSSLHandshake(void)
{
int ret, flags;
if (_socket < 0) return -1;
if (handshake_start_time == 0) handshake_start_time = millis();
ret = mbedtls_ssl_handshake(&ssl_ctx);
if (ret != 0) {
// Something happened before SSL handshake could be completed
// Negotiation error, other than socket not readable/writable when required
if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) {
return handle_error(ret);
}
// Handshake is taking too long
if ((millis()-handshake_start_time) > handshake_timeout)
return -1;
// Either MBEDTLS_ERR_SSL_WANT_READ or MBEDTLS_ERR_SSL_WANT_WRITE
return ret;
}
// Handshake completed, validate remote side if required...
if (_have_client_cert && _have_client_key) {
log_d("Protocol is %s Ciphersuite is %s", mbedtls_ssl_get_version(&ssl_ctx), mbedtls_ssl_get_ciphersuite(&ssl_ctx));
if ((ret = mbedtls_ssl_get_record_expansion(&ssl_ctx)) >= 0) {
log_d("Record expansion is %d", ret);
} else {
log_w("Record expansion is unknown (compression)");
}
}
log_v("Verifying peer X.509 certificate...");
if ((flags = mbedtls_ssl_get_verify_result(&ssl_ctx)) != 0) {
char buf[512];
memset(buf, 0, sizeof(buf));
mbedtls_x509_crt_verify_info(buf, sizeof(buf), " ! ", flags);
log_e("Failed to verify peer certificate! verification info: %s", buf);
_deleteHandshakeCerts();
return handle_error(ret);
} else {
log_v("Certificate verified.");
}
_deleteHandshakeCerts();
log_v("Free internal heap after TLS %u", ESP.getFreeHeap());
return 0;
}
int AsyncTCP_TLS_Context::write(const uint8_t *data, size_t len)
{
if (_socket < 0) return -1;
log_v("Writing packet, %d bytes unencrypted...", len);
int ret = mbedtls_ssl_write(&ssl_ctx, data, len);
if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE && ret < 0) {
log_v("Handling error %d", ret); //for low level debug
return handle_error(ret);
}
return ret;
}
int AsyncTCP_TLS_Context::read(uint8_t * data, size_t len)
{
int ret = mbedtls_ssl_read(&ssl_ctx, data, len);
if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE && ret < 0) {
log_v("Handling error %d", ret); //for low level debug
return handle_error(ret);
}
if (ret > 0) log_v("Read packet, %d out of %d requested bytes...", ret, len);
return ret;
}
void AsyncTCP_TLS_Context::_deleteHandshakeCerts(void)
{
if (_have_ca_cert) {
log_v("Cleaning CA certificate.");
mbedtls_x509_crt_free(&ca_cert);
_have_ca_cert = false;
}
if (_have_client_cert) {
log_v("Cleaning client certificate.");
mbedtls_x509_crt_free(&client_cert);
_have_client_cert = false;
}
if (_have_client_key) {
log_v("Cleaning client certificate key.");
mbedtls_pk_free(&client_key);
_have_client_key = false;
}
}
AsyncTCP_TLS_Context::~AsyncTCP_TLS_Context()
{
_deleteHandshakeCerts();
log_v("Cleaning SSL connection.");
mbedtls_ssl_free(&ssl_ctx);
mbedtls_ssl_config_free(&ssl_conf);
mbedtls_ctr_drbg_free(&drbg_ctx);
mbedtls_entropy_free(&entropy_ctx); // <-- Is this OK to free if mbedtls_entropy_init() has not been called on it?
}
#endif
#endif // ASYNC_TCP_SSL_ENABLED

View file

@ -0,0 +1,79 @@
#pragma once
#if ASYNC_TCP_SSL_ENABLED
#include "mbedtls/version.h"
#include "mbedtls/platform.h"
#if MBEDTLS_VERSION_NUMBER < 0x03000000
#include "mbedtls/net.h"
#else
#include "mbedtls/net_sockets.h"
#endif
#include "mbedtls/debug.h"
#include "mbedtls/ssl.h"
#include "mbedtls/entropy.h"
#include "mbedtls/ctr_drbg.h"
#include "mbedtls/error.h"
#define ASYNCTCP_TLS_CAN_RETRY(r) (((r) == MBEDTLS_ERR_SSL_WANT_READ) || ((r) == MBEDTLS_ERR_SSL_WANT_WRITE))
#define ASYNCTCP_TLS_EOF(r) (((r) == MBEDTLS_ERR_SSL_CONN_EOF) || ((r) == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY))
class AsyncTCP_TLS_Context
{
private:
// These fields must persist for the life of the encrypted connection, destroyed on
// object destructor.
mbedtls_ssl_context ssl_ctx;
mbedtls_ssl_config ssl_conf;
mbedtls_ctr_drbg_context drbg_ctx;
mbedtls_entropy_context entropy_ctx;
// These allocate memory during handshake but must be freed on either success or failure
mbedtls_x509_crt ca_cert;
mbedtls_x509_crt client_cert;
mbedtls_pk_context client_key;
bool _have_ca_cert;
bool _have_client_cert;
bool _have_client_key;
unsigned long handshake_timeout;
unsigned long handshake_start_time;
int _socket;
int _startSSLClient(int sck, const char * host_or_ip,
const unsigned char *rootCABuff, const size_t rootCABuff_len,
const unsigned char *cli_cert, const size_t cli_cert_len,
const unsigned char *cli_key, const size_t cli_key_len,
const char *pskIdent, const char *psKey,
bool insecure);
// Delete certificates used in handshake
void _deleteHandshakeCerts(void);
public:
AsyncTCP_TLS_Context(void);
virtual ~AsyncTCP_TLS_Context();
int startSSLClientInsecure(int sck, const char * host_or_ip);
int startSSLClient(int sck, const char * host_or_ip,
const char *pskIdent, const char *psKey);
int startSSLClient(int sck, const char * host_or_ip,
const char *rootCABuff,
const char *cli_cert,
const char *cli_key);
int startSSLClient(int sck, const char * host_or_ip,
const unsigned char *rootCABuff, const size_t rootCABuff_len,
const unsigned char *cli_cert, const size_t cli_cert_len,
const unsigned char *cli_key, const size_t cli_key_len);
int runSSLHandshake(void);
int write(const uint8_t *data, size_t len);
int read(uint8_t * data, size_t len);
};
#endif // ASYNC_TCP_SSL_ENABLED

View file

@ -264,15 +264,15 @@ int CAN_init() {
return 0;
}
int CAN_write_frame(const CAN_frame_t *p_frame) {
bool CAN_write_frame(const CAN_frame_t *p_frame) {
if (sem_tx_complete == NULL) {
return -1;
return false;
}
// Write the frame to the controller
CAN_write_frame_phy(p_frame);
return xSemaphoreTake(sem_tx_complete, 20) == pdTRUE ? 0 : -1;
return xSemaphoreTake(sem_tx_complete, 20) == pdTRUE ? true : false;
}
int CAN_stop() {

View file

@ -93,6 +93,10 @@ typedef struct {
uint8_t AMR3; /**< \brief Acceptance Mask Register AMR3 */
} CAN_filter_t;
extern void gpio_matrix_in(uint32_t gpio, uint32_t signal_idx, bool inv);
extern void gpio_matrix_out(uint32_t gpio, uint32_t signal_idx, bool out_inv, bool oen_inv);
extern void gpio_pad_select_gpio(uint8_t gpio_num);
/**
* \brief Initialize the CAN Module
*
@ -104,9 +108,9 @@ int CAN_init(void);
* \brief Send a can frame
*
* \param p_frame Pointer to the frame to be send, see #CAN_frame_t
* \return 0 Frame has been written to the module
* \return 1 Frame has been written to the module
*/
int CAN_write_frame(const CAN_frame_t *p_frame);
bool CAN_write_frame(const CAN_frame_t *p_frame);
/**
* \brief Stops the CAN Module

View file

@ -5,28 +5,10 @@
int ESP32CAN::CANInit() {
return CAN_init();
}
int ESP32CAN::CANWriteFrame(const CAN_frame_t* p_frame) {
static unsigned long start_time;
int result = -1;
if (tx_ok) {
result = CAN_write_frame(p_frame);
tx_ok = (result == 0) ? true : false;
if (tx_ok == false) {
#ifdef DEBUG_VIA_USB
Serial.println("CAN failure! Check wires");
#endif
set_event(EVENT_CAN_NATIVE_TX_FAILURE, 0);
start_time = millis();
} else {
clear_event(EVENT_CAN_NATIVE_TX_FAILURE);
}
} else {
if ((millis() - start_time) >= 20) {
tx_ok = true;
}
}
return result;
bool ESP32CAN::CANWriteFrame(const CAN_frame_t* p_frame) {
return CAN_write_frame(p_frame);
}
int ESP32CAN::CANStop() {
return CAN_stop();
}

View file

@ -9,7 +9,7 @@ class ESP32CAN {
bool tx_ok = true;
int CANInit();
int CANConfigFilter(const CAN_filter_t* p_filter);
int CANWriteFrame(const CAN_frame_t* p_frame);
bool CANWriteFrame(const CAN_frame_t* p_frame);
int CANStop();
void CANSetCfg(CAN_device_t* can_cfg);
};