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; volatile unsigned long long bmsResetTimeOffset = 0;
// The current software version, shown on webserver // The current software version, shown on webserver
const char* version_number = "8.8.dev"; const char* version_number = "8.9.dev";
// Interval settings // Interval settings
uint16_t intervalUpdateValues = INTERVAL_1_S; // Interval at which to update inverter values / Modbus registers uint16_t intervalUpdateValues = INTERVAL_1_S; // Interval at which to update inverter values / Modbus registers
unsigned long previousMillis10ms = 0; unsigned long previousMillis10ms = 0;
unsigned long previousMillisUpdateVal = 0; unsigned long previousMillisUpdateVal = 0;
unsigned long lastMillisOverflowCheck = 0;
// Task time measurement for debugging and for setting CPU load events // Task time measurement for debugging and for setting CPU load events
int64_t core_task_time_us; int64_t core_task_time_us;
MyTimer core_task_timer_10s(INTERVAL_10_S); MyTimer core_task_timer_10s(INTERVAL_10_S);
@ -146,17 +147,8 @@ void setup() {
#endif #endif
} }
// Perform main program functions // Loop empty, all functionality runs in tasks
void loop() { 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
}
#if defined(LOG_CAN_TO_SD) || defined(LOG_TO_SD) #if defined(LOG_CAN_TO_SD) || defined(LOG_TO_SD)
void logging_loop(void* task_time_us) { 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; datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery2.status.remaining_capacity_Wh;
} }
#endif // DOUBLE_BATTERY #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() { void update_values_inverter() {

View file

@ -1060,10 +1060,6 @@ void transmit_can_battery() {
case CELL_VOLTAGE_CELLNO: case CELL_VOLTAGE_CELLNO:
current_cell_polled++; current_cell_polled++;
if (current_cell_polled > 96) { 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; cmdState = CELL_VOLTAGE_CELLNO_LAST;
} else { } else {
cmdState = CELL_VOLTAGE_CELLNO; 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.min_design_voltage_dV = MIN_PACK_VOLTAGE_60AH;
datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV; datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
datalayer.system.status.battery_allows_contactor_closing = true; datalayer.system.status.battery_allows_contactor_closing = true;
datalayer.battery.info.number_of_cells = NUMBER_OF_CELLS;
#ifdef DOUBLE_BATTERY #ifdef DOUBLE_BATTERY
datalayer.battery2.info.max_design_voltage_dV = datalayer.battery.info.max_design_voltage_dV; 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.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.info.max_cell_voltage_deviation_mV = datalayer.battery.info.max_cell_voltage_deviation_mV;
datalayer.battery2.status.voltage_dV = datalayer.battery2.status.voltage_dV =
0; //Init voltage to 0 to allow contactor check to operate without fear of default values colliding 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 #endif
pinMode(WUP_PIN1, OUTPUT); pinMode(WUP_PIN1, OUTPUT);
digitalWrite(WUP_PIN1, HIGH); // Wake up the battery 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 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 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 MIN_PACK_VOLTAGE_120AH 2680 // Discharge stops if pack voltage exceeds this value
#define NUMBER_OF_CELLS 96
void setup_battery(void); void setup_battery(void);
void transmit_can_frame(CAN_frame* tx_frame, int interface); 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 uint8_t rxConsecutiveFrames = 0;
static uint16_t min_max_voltage[2]; //contains cell min[0] and max[1] values in mV static uint16_t min_max_voltage[2]; //contains cell min[0] and max[1] values in mV
static uint8_t cellcounter = 0; static uint8_t cellcounter = 0;
static uint32_t remaining_capacity = 0;
static uint16_t cell_voltages[108]; //array with all the cellvoltages static uint16_t cell_voltages[108]; //array with all the cellvoltages
static bool startedUp = false; static bool startedUp = false;
static uint8_t DTC_reset_counter = 0; 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; 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 //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 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.voltage_dV = BATT_U * 10;
datalayer.battery.status.current_dA = BATT_I * 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_discharge_power_W = HvBattPwrLimDchaSlowAgi * 1000; //kW to W
datalayer.battery.status.max_charge_power_W = HvBattPwrLimChrgSlowAgi * 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]; 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 #ifdef DEBUG_LOG
logging.print("BMS reported SOC%: "); logging.print("BMS reported SOC%: ");
logging.println(SOC_BMS); logging.println(SOC_BMS);
@ -463,11 +478,12 @@ void transmit_can_battery() {
} }
void setup_battery(void) { // Performs one time setup at startup 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.system.info.battery_protocol[63] = '\0';
datalayer.battery.info.number_of_cells = 108; datalayer.battery.info.number_of_cells = 0; // Initializes when all cells have been read
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV; datalayer.battery.info.total_capacity_Wh = 78200; //Startout in 78kWh mode (This value used for SOC calc)
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV; 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.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
datalayer.battery.info.min_cell_voltage_mV = MIN_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; datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;

View file

@ -4,8 +4,10 @@
#include "../include.h" #include "../include.h"
#define BATTERY_SELECTED #define BATTERY_SELECTED
#define MAX_PACK_VOLTAGE_DV 4540 //5000 = 500.0V #define MAX_PACK_VOLTAGE_108S_DV 4540
#define MIN_PACK_VOLTAGE_DV 2938 #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_DEVIATION_MV 250
#define MAX_CELL_VOLTAGE_MV 4210 //Battery is put into emergency stop if one cell goes over this value #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 #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 CAN_device_t CAN_cfg; // CAN Config
const int rx_queue_size = 10; // Receive Queue size 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_2515 = 0;
volatile bool send_ok_2518 = 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++) { for (uint8_t i = 0; i < tx_frame->DLC; i++) {
frame.data.u8[i] = tx_frame->data.u8[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; break;
case CAN_ADDON_MCP2515: { case CAN_ADDON_MCP2515: {
#ifdef CAN_ADDON #ifdef CAN_ADDON
@ -162,9 +166,7 @@ void transmit_can_frame(CAN_frame* tx_frame, int interface) {
send_ok_2515 = can.tryToSend(MCP2515Frame); send_ok_2515 = can.tryToSend(MCP2515Frame);
if (!send_ok_2515) { if (!send_ok_2515) {
set_event(EVENT_CAN_BUFFER_FULL, interface); datalayer.system.info.can_2515_send_fail = true;
} else {
clear_event(EVENT_CAN_BUFFER_FULL);
} }
#else // Interface not compiled, and settings try to use it #else // Interface not compiled, and settings try to use it
set_event(EVENT_INTERFACE_MISSING, interface); 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); send_ok_2518 = canfd.tryToSend(MCP2518Frame);
if (!send_ok_2518) { if (!send_ok_2518) {
set_event(EVENT_CANFD_BUFFER_FULL, interface); datalayer.system.info.can_2518_send_fail = true;
} else {
clear_event(EVENT_CANFD_BUFFER_FULL);
} }
#else // Interface not compiled, and settings try to use it #else // Interface not compiled, and settings try to use it
set_event(EVENT_INTERFACE_MISSING, interface); set_event(EVENT_INTERFACE_MISSING, interface);

View file

@ -228,10 +228,18 @@ typedef struct {
uint8_t can_replay_interface = CAN_NATIVE; uint8_t can_replay_interface = CAN_NATIVE;
/** bool, determines if CAN replay should loop or not */ /** bool, determines if CAN replay should loop or not */
bool loop_playback = false; 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; } DATALAYER_SYSTEM_INFO_TYPE;
typedef struct { typedef struct {
/** Millis rollover count. Increments every 49.7 days. Used for keeping track on events */
uint8_t millisrolloverCount = 0;
#ifdef FUNCTION_TIME_MEASUREMENT #ifdef FUNCTION_TIME_MEASUREMENT
/** Core task measurement variable */ /** Core task measurement variable */
int64_t core_task_max_us = 0; int64_t core_task_max_us = 0;
@ -241,8 +249,6 @@ typedef struct {
int64_t mqtt_task_10s_max_us = 0; int64_t mqtt_task_10s_max_us = 0;
/** Wifi sub-task measurement variable, reset each 10 seconds */ /** Wifi sub-task measurement variable, reset each 10 seconds */
int64_t wifi_task_10s_max_us = 0; 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 */ /** OTA handling function measurement variable */
int64_t time_ota_us = 0; int64_t time_ota_us = 0;

View file

@ -19,6 +19,26 @@ battery_pause_status emulator_pause_status = NORMAL;
//battery pause status end //battery pause status end
void update_machineryprotection() { 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! // 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 // 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_ENUM_TYPE_STRING[] = {EVENTS_ENUM_TYPE(GENERATE_STRING)};
static const char* EVENTS_LEVEL_TYPE_STRING[] = {EVENTS_LEVEL_TYPE(GENERATE_STRING)}; static const char* EVENTS_LEVEL_TYPE_STRING[] = {EVENTS_LEVEL_TYPE(GENERATE_STRING)};
static uint32_t lastMillis = millis();
/* Local function prototypes */ /* Local function prototypes */
static void set_event(EVENTS_ENUM_TYPE event, uint8_t data, bool latched); static void set_event(EVENTS_ENUM_TYPE event, uint8_t data, bool latched);
static void update_event_level(void); 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 log_event(EVENTS_ENUM_TYPE event, uint8_t millisrolloverCount, uint32_t timestamp, uint8_t data);
static void print_event_log(void); 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 */ /* Initialization function */
void init_events(void) { 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_BUFFER_FULL].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_CAN_OVERRUN].level = EVENT_LEVEL_INFO; events.entries[EVENT_CAN_OVERRUN].level = EVENT_LEVEL_INFO;
events.entries[EVENT_CAN_CORRUPTED_WARNING].level = EVENT_LEVEL_WARNING; 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_BATTERY_MISSING].level = EVENT_LEVEL_ERROR;
events.entries[EVENT_CAN_BATTERY2_MISSING].level = EVENT_LEVEL_WARNING; events.entries[EVENT_CAN_BATTERY2_MISSING].level = EVENT_LEVEL_WARNING;
events.entries[EVENT_CAN_CHARGER_MISSING].level = EVENT_LEVEL_INFO; 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: case EVENT_CANMCP2515_INIT_FAILURE:
return "CAN-MCP addon initialization failed. Check hardware"; return "CAN-MCP addon initialization failed. Check hardware";
case EVENT_CANFD_BUFFER_FULL: 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: 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: case EVENT_CAN_OVERRUN:
return "CAN message failed to send within defined time. Contact developers, CPU load might be too high."; return "CAN message failed to send within defined time. Contact developers, CPU load might be too high.";
case EVENT_CAN_CORRUPTED_WARNING: 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 // We should set the event, update event info
events.entries[event].timestamp = millis(); events.entries[event].timestamp = millis();
events.entries[event].millisrolloverCount = millisrolloverCount; events.entries[event].millisrolloverCount = datalayer.system.status.millisrolloverCount;
events.entries[event].data = data; events.entries[event].data = data;
// Check if the event is latching // Check if the event is latching
events.entries[event].state = latched ? EVENT_STATE_ACTIVE_LATCHED : EVENT_STATE_ACTIVE; 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; int entry_address = EE_EVENT_ENTRY_START_ADDRESS + EE_EVENT_ENTRY_SIZE * events.event_log_head_index;
// Prepare an event block to write // Prepare an event block to write
EVENT_LOG_ENTRY_TYPE entry = { EVENT_LOG_ENTRY_TYPE entry = {.event = event,
.event = event, .millisrolloverCount = millisrolloverCount, .timestamp = timestamp, .data = data}; .millisrolloverCount = datalayer.system.status.millisrolloverCount,
.timestamp = timestamp,
.data = data};
// Put the event in (what I guess is) the RAM EEPROM mirror, or write buffer // Put the event in (what I guess is) the RAM EEPROM mirror, or write buffer
EEPROM.put(entry_address, entry); EEPROM.put(entry_address, entry);

View file

@ -159,8 +159,6 @@ struct EventData {
const EVENTS_STRUCT_TYPE* event_pointer; 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_enum_string(EVENTS_ENUM_TYPE event);
const char* get_event_message_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); 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); const EVENTS_STRUCT_TYPE* get_event_pointer(EVENTS_ENUM_TYPE event);
void run_event_handling(void);
void run_sequence_on_target(void); void run_sequence_on_target(void);
bool compareEventsByTimestampAsc(const EventData& a, const EventData& b); bool compareEventsByTimestampAsc(const EventData& a, const EventData& b);

View file

@ -1,4 +1,5 @@
#include "events_html.h" #include "events_html.h"
#include "../../datalayer/datalayer.h"
const char EVENTS_HTML_START[] = R"=====( 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> <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 class='event'>");
content.concat("<div>" + String(get_event_enum_string(event_handle)) + "</div>"); 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>" + 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>"); String(timestamp_now - event_pointer->timestamp) + "</div>");
content.concat("<div>" + String(event_pointer->occurences) + "</div>"); content.concat("<div>" + String(event_pointer->occurences) + "</div>");
content.concat("<div>" + String(event_pointer->data) + "</div>"); content.concat("<div>" + String(event_pointer->data) + "</div>");

View file

@ -940,8 +940,6 @@ String processor(const String& var) {
content += content +=
"<h4>WIFI function (MQTT task) max load last 10 s: " + String(datalayer.system.status.wifi_task_10s_max_us) + "<h4>WIFI function (MQTT task) max load last 10 s: " + String(datalayer.system.status.wifi_task_10s_max_us) +
" us</h4>"; " 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>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>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>"; 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 <Preferences.h>
#include <WiFi.h> #include <WiFi.h>
#include "../../include.h" #include "../../include.h"
#include "../../lib/ESP32Async-AsyncTCP/src/AsyncTCP.h"
#include "../../lib/ESP32Async-ESPAsyncWebServer/src/ESPAsyncWebServer.h" #include "../../lib/ESP32Async-ESPAsyncWebServer/src/ESPAsyncWebServer.h"
#include "../../lib/YiannisBourkelis-Uptime-Library/src/uptime_formatter.h" #include "../../lib/YiannisBourkelis-Uptime-Library/src/uptime_formatter.h"
#include "../../lib/ayushsharma82-ElegantOTA/src/ElegantOTA.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 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" "src"
) )
set(COMPONENT_REQUIRES
"arduino-esp32"
"AsyncTCP"
)
register_component() 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 We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender 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 nationality, personal appearance, race, religion, or sexual identity
and orientation. and orientation.

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
{ {
"name": "ESPAsyncWebServer", "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.", "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", "keywords": "http,async,websocket,webserver",
"homepage": "https://github.com/ESP32Async/ESPAsyncWebServer", "homepage": "https://github.com/ESP32Async/ESPAsyncWebServer",
@ -24,7 +24,7 @@
{ {
"owner": "ESP32Async", "owner": "ESP32Async",
"name": "AsyncTCP", "name": "AsyncTCP",
"version": "^3.3.2", "version": "^3.3.6",
"platforms": "espressif32" "platforms": "espressif32"
}, },
{ {
@ -38,9 +38,9 @@
"platforms": "espressif8266" "platforms": "espressif8266"
}, },
{ {
"owner": "khoih-prog", "owner": "ayushsharma82",
"name": "AsyncTCP_RP2040W", "name": "RPAsyncTCP",
"version": "^1.2.0", "version": "^1.3.1",
"platforms": "raspberrypi" "platforms": "raspberrypi"
} }
], ],

View file

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

View file

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

View file

@ -1,64 +1,48 @@
/* // SPDX-License-Identifier: LGPL-3.0-or-later
Asynchronous WebServer library for Espressif MCUs // 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_ #ifndef ASYNCEVENTSOURCE_H_
#define ASYNCEVENTSOURCE_H_ #define ASYNCEVENTSOURCE_H_
#include <Arduino.h> #include <Arduino.h>
#ifdef ESP32 #ifdef ESP32
#include "../../ESP32Async-AsyncTCP/src/AsyncTCP.h" #include "../../mathieucarbou-AsyncTCPSock/src/AsyncTCP.h"
#include <mutex> #include <mutex>
#ifndef SSE_MAX_QUEUED_MESSAGES #ifndef SSE_MAX_QUEUED_MESSAGES
#define SSE_MAX_QUEUED_MESSAGES 32 #define SSE_MAX_QUEUED_MESSAGES 32
#endif #endif
#define SSE_MIN_INFLIGH 2 * 1460 // allow 2 MSS packets #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 #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) #elif defined(ESP8266)
#include <ESPAsyncTCP.h> #include <ESPAsyncTCP.h>
#ifndef SSE_MAX_QUEUED_MESSAGES #ifndef SSE_MAX_QUEUED_MESSAGES
#define SSE_MAX_QUEUED_MESSAGES 8 #define SSE_MAX_QUEUED_MESSAGES 8
#endif #endif
#define SSE_MIN_INFLIGH 2 * 1460 // allow 2 MSS packets #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 #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) #elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <AsyncTCP_RP2040W.h> #include <RPAsyncTCP.h>
#ifndef SSE_MAX_QUEUED_MESSAGES #ifndef SSE_MAX_QUEUED_MESSAGES
#define SSE_MAX_QUEUED_MESSAGES 32 #define SSE_MAX_QUEUED_MESSAGES 32
#endif #endif
#define SSE_MIN_INFLIGH 2 * 1460 // allow 2 MSS packets #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 #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 #endif
#include "ESPAsyncWebServer.h" #include "ESPAsyncWebServer.h"
#ifdef ESP8266 #ifdef ESP8266
#include <Hash.h> #include <Hash.h>
#ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library #ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library
#include <../src/Hash.h> #include <../src/Hash.h>
#endif #endif
#endif #endif
class AsyncEventSource; class AsyncEventSource;
class AsyncEventSourceResponse; class AsyncEventSourceResponse;
class AsyncEventSourceClient; class AsyncEventSourceClient;
using ArEventHandlerFunction = std::function<void(AsyncEventSourceClient* client)>; using ArEventHandlerFunction = std::function<void(AsyncEventSourceClient *client)>;
using ArAuthorizeConnectHandler = ArAuthorizeFunction; using ArAuthorizeConnectHandler = ArAuthorizeFunction;
// shared message object container // shared message object container
using AsyncEvent_SharedData_t = std::shared_ptr<String>; using AsyncEvent_SharedData_t = std::shared_ptr<String>;
@ -69,55 +53,67 @@ using AsyncEvent_SharedData_t = std::shared_ptr<String>;
*/ */
class AsyncEventSourceMessage { class AsyncEventSourceMessage {
private: private:
const AsyncEvent_SharedData_t _data; const AsyncEvent_SharedData_t _data;
size_t _sent{0}; // num of bytes already sent size_t _sent{0}; // num of bytes already sent
size_t _acked{0}; // num of bytes acked size_t _acked{0}; // num of bytes acked
public: public:
AsyncEventSourceMessage(AsyncEvent_SharedData_t data) : _data(data) {}; AsyncEventSourceMessage(AsyncEvent_SharedData_t data) : _data(data){};
#ifdef ESP32 #if defined(ESP32)
AsyncEventSourceMessage(const char* data, size_t len) : _data(std::make_shared<String>(data, len)) {}; 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 #else
// esp8266's String does not have constructor with data/length arguments. Use a concat method here // 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); }; AsyncEventSourceMessage(const char *data, size_t len) {
_data->concat(data, len);
};
#endif #endif
/** /**
* @brief acknowledge sending len bytes of data * @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 * @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 * @param time
* @return size_t number of extra bytes carried over * @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 * @brief write message data to client's buffer
* @note this method does NOT call client's send * @note this method does NOT call client's send
* *
* @param client * @param client
* @return size_t number of bytes written * @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 * @brief writes message data to client's buffer and calls client's send method
* *
* @param client * @param client
* @return size_t returns num of bytes the clien was able to send() * @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 // returns true if full message's length were acked
bool finished() { return _acked == _data->length(); } bool finished() {
return _acked == _data->length();
}
/** /**
* @brief returns true if all data has been sent already * @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 { class AsyncEventSourceClient {
private: private:
AsyncClient* _client; AsyncClient *_client;
AsyncEventSource* _server; AsyncEventSource *_server;
uint32_t _lastId{0}; uint32_t _lastId{0};
size_t _inflight{0}; // num of unacknowledged bytes that has been written to socket buffer 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 size_t _max_inflight{SSE_MAX_INFLIGH}; // max num of unacknowledged bytes that could be written to socket buffer
std::list<AsyncEventSourceMessage> _messageQueue; std::list<AsyncEventSourceMessage> _messageQueue;
#ifdef ESP32 #ifdef ESP32
mutable std::mutex _lockmq; mutable std::mutex _lockmq;
#endif #endif
bool _queueMessage(const char* message, size_t len); bool _queueMessage(const char *message, size_t len);
bool _queueMessage(AsyncEvent_SharedData_t&& msg); bool _queueMessage(AsyncEvent_SharedData_t &&msg);
void _runQueue(); void _runQueue();
public: public:
AsyncEventSourceClient(AsyncWebServerRequest* request, AsyncEventSource* server); AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server);
~AsyncEventSourceClient(); ~AsyncEventSourceClient();
/** /**
* @brief Send an SSE message to client * @brief Send an SSE message to client
* it will craft an SSE message and place it to client's message queue * 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 true if message was placed in a queue
* @return false if queue is full * @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 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 String &event, uint32_t id = 0, uint32_t reconnect = 0) {
bool send(const String& message, const char* event, uint32_t id = 0, uint32_t reconnect = 0) { return send(message.c_str(), event, id, reconnect); } 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 * @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 * @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 true on success
* @return false on queue overflow or no client connected * @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")]] [[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); }; bool write(const char *message, size_t len) {
return connected() && _queueMessage(message, len);
};
// close client's connection // close client's connection
void close(); void close();
// getters // getters
AsyncClient* client() { return _client; } AsyncClient *client() {
bool connected() const { return _client && _client->connected(); } return _client;
uint32_t lastId() const { return _lastId; } }
size_t packetsWaiting() const { return _messageQueue.size(); }; 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 * @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 * 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 * @note actual amount of data written could possible be a bit larger but no more than available socket buff space
* *
* @param value * @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 * @brief Get current max inflight bytes value
* *
* @return size_t * @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!) // system callbacks (do not call if from user code!)
void _onAck(size_t len, uint32_t time); void _onAck(size_t len, uint32_t time);
void _onPoll(); void _onPoll();
void _onTimeout(uint32_t time); void _onTimeout(uint32_t time);
void _onDisconnect(); void _onDisconnect();
}; };
/** /**
@ -210,44 +224,50 @@ class AsyncEventSourceClient {
* *
*/ */
class AsyncEventSource : public AsyncWebHandler { class AsyncEventSource : public AsyncWebHandler {
private: private:
String _url; String _url;
std::list<std::unique_ptr<AsyncEventSourceClient>> _clients; std::list<std::unique_ptr<AsyncEventSourceClient>> _clients;
#ifdef ESP32 #ifdef ESP32
// Same as for individual messages, protect mutations of _clients list // Same as for individual messages, protect mutations of _clients list
// since simultaneous access from different tasks is possible // since simultaneous access from different tasks is possible
mutable std::mutex _client_queue_lock; mutable std::mutex _client_queue_lock;
#endif #endif
ArEventHandlerFunction _connectcb = nullptr; ArEventHandlerFunction _connectcb = nullptr;
ArEventHandlerFunction _disconnectcb = nullptr; ArEventHandlerFunction _disconnectcb = nullptr;
// this method manipulates in-fligh data size for connected client depending on number of active connections // this method manipulates in-fligh data size for connected client depending on number of active connections
void _adjust_inflight_window(); void _adjust_inflight_window();
public: public:
typedef enum { typedef enum {
DISCARDED = 0, DISCARDED = 0,
ENQUEUED = 1, ENQUEUED = 1,
PARTIALLY_ENQUEUED = 2, PARTIALLY_ENQUEUED = 2,
} SendStatus; } SendStatus;
AsyncEventSource(const char* url) : _url(url) {}; AsyncEventSource(const char *url) : _url(url){};
AsyncEventSource(const String& url) : _url(url) {}; AsyncEventSource(const String &url) : _url(url){};
~AsyncEventSource() { close(); }; ~AsyncEventSource() {
close();
};
const char* url() const { return _url.c_str(); } const char *url() const {
// close all connected clients return _url.c_str();
void close(); }
// close all connected clients
void close();
/** /**
* @brief set on-connect callback for the client * @brief set on-connect callback for the client
* used to deliver messages to client on first connect * used to deliver messages to client on first connect
* *
* @param cb * @param cb
*/ */
void onConnect(ArEventHandlerFunction cb) { _connectcb = cb; } void onConnect(ArEventHandlerFunction cb) {
_connectcb = cb;
}
/** /**
* @brief Send an SSE message to client * @brief Send an SSE message to client
* it will craft an SSE message and place it to all connected client's message queues * 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 * @param reconnect client's reconnect timeout
* @return SendStatus if message was placed in any/all/part of the client's queues * @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 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 String &event, uint32_t id = 0, uint32_t reconnect = 0) {
SendStatus send(const String& message, const char* event, uint32_t id = 0, uint32_t reconnect = 0) { return send(message.c_str(), event, id, reconnect); } 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 ! // 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 onDisconnect(ArEventHandlerFunction cb) {
void authorizeConnect(ArAuthorizeConnectHandler cb); _disconnectcb = cb;
}
void authorizeConnect(ArAuthorizeConnectHandler cb);
// returns number of connected clients // returns number of connected clients
size_t count() const; size_t count() const;
// returns average number of messages pending in all client's queues // returns average number of messages pending in all client's queues
size_t avgPacketsWaiting() const; size_t avgPacketsWaiting() const;
// system callbacks (do not call from user code!) // system callbacks (do not call from user code!)
void _addClient(AsyncEventSourceClient* client); void _addClient(AsyncEventSourceClient *client);
void _handleDisconnect(AsyncEventSourceClient* client); void _handleDisconnect(AsyncEventSourceClient *client);
bool canHandle(AsyncWebServerRequest* request) const override final; bool canHandle(AsyncWebServerRequest *request) const override final;
void handleRequest(AsyncWebServerRequest* request) override final; void handleRequest(AsyncWebServerRequest *request) override final;
}; };
class AsyncEventSourceResponse : public AsyncWebServerResponse { class AsyncEventSourceResponse : public AsyncWebServerResponse {
private: private:
AsyncEventSource* _server; AsyncEventSource *_server;
public: public:
AsyncEventSourceResponse(AsyncEventSource* server); AsyncEventSourceResponse(AsyncEventSource *server);
void _respond(AsyncWebServerRequest* request); void _respond(AsyncWebServerRequest *request);
size_t _ack(AsyncWebServerRequest* request, size_t len, uint32_t time); size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time);
bool _sourceValid() const { return true; } bool _sourceValid() const {
return true;
}
}; };
#endif /* ASYNCEVENTSOURCE_H_ */ #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" #include "AsyncJson.h"
#if ASYNC_JSON_SUPPORT == 1 #if ASYNC_JSON_SUPPORT == 1
#if ARDUINOJSON_VERSION_MAJOR == 5 #if ARDUINOJSON_VERSION_MAJOR == 5
AsyncJsonResponse::AsyncJsonResponse(bool isArray) : _isValid{false} { AsyncJsonResponse::AsyncJsonResponse(bool isArray) : _isValid{false} {
_code = 200; _code = 200;
_contentType = asyncsrv::T_application_json; _contentType = asyncsrv::T_application_json;
if (isArray) if (isArray) {
_root = _jsonBuffer.createArray(); _root = _jsonBuffer.createArray();
else } else {
_root = _jsonBuffer.createObject(); _root = _jsonBuffer.createObject();
}
} }
#elif ARDUINOJSON_VERSION_MAJOR == 6 #elif ARDUINOJSON_VERSION_MAJOR == 6
AsyncJsonResponse::AsyncJsonResponse(bool isArray, size_t maxJsonBufferSize) : _jsonBuffer(maxJsonBufferSize), _isValid{false} { AsyncJsonResponse::AsyncJsonResponse(bool isArray, size_t maxJsonBufferSize) : _jsonBuffer(maxJsonBufferSize), _isValid{false} {
_code = 200; _code = 200;
_contentType = asyncsrv::T_application_json; _contentType = asyncsrv::T_application_json;
if (isArray) if (isArray) {
_root = _jsonBuffer.createNestedArray(); _root = _jsonBuffer.createNestedArray();
else } else {
_root = _jsonBuffer.createNestedObject(); _root = _jsonBuffer.createNestedObject();
}
} }
#else #else
AsyncJsonResponse::AsyncJsonResponse(bool isArray) : _isValid{false} { AsyncJsonResponse::AsyncJsonResponse(bool isArray) : _isValid{false} {
_code = 200; _code = 200;
_contentType = asyncsrv::T_application_json; _contentType = asyncsrv::T_application_json;
if (isArray) if (isArray) {
_root = _jsonBuffer.add<JsonArray>(); _root = _jsonBuffer.add<JsonArray>();
else } else {
_root = _jsonBuffer.add<JsonObject>(); _root = _jsonBuffer.add<JsonObject>();
}
} }
#endif #endif
size_t AsyncJsonResponse::setLength() { size_t AsyncJsonResponse::setLength() {
#if ARDUINOJSON_VERSION_MAJOR == 5 #if ARDUINOJSON_VERSION_MAJOR == 5
_contentLength = _root.measureLength(); _contentLength = _root.measureLength();
#else #else
_contentLength = measureJson(_root); _contentLength = measureJson(_root);
#endif #endif
if (_contentLength) { if (_contentLength) {
_isValid = true; _isValid = true;
} }
return _contentLength; 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); ChunkPrint dest(data, _sentLength, len);
#if ARDUINOJSON_VERSION_MAJOR == 5 #if ARDUINOJSON_VERSION_MAJOR == 5
_root.printTo(dest); _root.printTo(dest);
#else #else
serializeJson(_root, dest); serializeJson(_root, dest);
#endif #endif
return len; return len;
} }
#if ARDUINOJSON_VERSION_MAJOR == 6 #if ARDUINOJSON_VERSION_MAJOR == 6
PrettyAsyncJsonResponse::PrettyAsyncJsonResponse(bool isArray, size_t maxJsonBufferSize) : AsyncJsonResponse{isArray, maxJsonBufferSize} {} PrettyAsyncJsonResponse::PrettyAsyncJsonResponse(bool isArray, size_t maxJsonBufferSize) : AsyncJsonResponse{isArray, maxJsonBufferSize} {}
#else #else
PrettyAsyncJsonResponse::PrettyAsyncJsonResponse(bool isArray) : AsyncJsonResponse{isArray} {} PrettyAsyncJsonResponse::PrettyAsyncJsonResponse(bool isArray) : AsyncJsonResponse{isArray} {}
#endif #endif
size_t PrettyAsyncJsonResponse::setLength() { size_t PrettyAsyncJsonResponse::setLength() {
#if ARDUINOJSON_VERSION_MAJOR == 5 #if ARDUINOJSON_VERSION_MAJOR == 5
_contentLength = _root.measurePrettyLength(); _contentLength = _root.measurePrettyLength();
#else #else
_contentLength = measureJsonPretty(_root); _contentLength = measureJsonPretty(_root);
#endif #endif
if (_contentLength) { if (_contentLength) {
_isValid = true; _isValid = true;
} }
return _contentLength; 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); ChunkPrint dest(data, _sentLength, len);
#if ARDUINOJSON_VERSION_MAJOR == 5 #if ARDUINOJSON_VERSION_MAJOR == 5
_root.prettyPrintTo(dest); _root.prettyPrintTo(dest);
#else #else
serializeJsonPretty(_root, dest); serializeJsonPretty(_root, dest);
#endif #endif
return len; return len;
} }
#if ARDUINOJSON_VERSION_MAJOR == 6 #if ARDUINOJSON_VERSION_MAJOR == 6
AsyncCallbackJsonWebHandler::AsyncCallbackJsonWebHandler(const String& uri, ArJsonRequestHandlerFunction onRequest, size_t maxJsonBufferSize) 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) {} : _uri(uri), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), maxJsonBufferSize(maxJsonBufferSize), _maxContentLength(16384) {}
#else #else
AsyncCallbackJsonWebHandler::AsyncCallbackJsonWebHandler(const String& uri, ArJsonRequestHandlerFunction onRequest) AsyncCallbackJsonWebHandler::AsyncCallbackJsonWebHandler(const String &uri, ArJsonRequestHandlerFunction onRequest)
: _uri(uri), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), _maxContentLength(16384) {} : _uri(uri), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), _maxContentLength(16384) {}
#endif #endif
bool AsyncCallbackJsonWebHandler::canHandle(AsyncWebServerRequest* request) const { bool AsyncCallbackJsonWebHandler::canHandle(AsyncWebServerRequest *request) const {
if (!_onRequest || !request->isHTTP() || !(_method & request->method())) if (!_onRequest || !request->isHTTP() || !(_method & request->method())) {
return false; return false;
}
if (_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri + "/"))) if (_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri + "/"))) {
return false; 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 false;
}
return true; return true;
} }
void AsyncCallbackJsonWebHandler::handleRequest(AsyncWebServerRequest* request) { void AsyncCallbackJsonWebHandler::handleRequest(AsyncWebServerRequest *request) {
if (_onRequest) { if (_onRequest) {
if (request->method() == HTTP_GET) { if (request->method() == HTTP_GET) {
JsonVariant json; JsonVariant json;
@ -110,21 +119,21 @@ void AsyncCallbackJsonWebHandler::handleRequest(AsyncWebServerRequest* request)
return; return;
} else if (request->_tempObject != NULL) { } else if (request->_tempObject != NULL) {
#if ARDUINOJSON_VERSION_MAJOR == 5 #if ARDUINOJSON_VERSION_MAJOR == 5
DynamicJsonBuffer jsonBuffer; DynamicJsonBuffer jsonBuffer;
JsonVariant json = jsonBuffer.parse((uint8_t*)(request->_tempObject)); JsonVariant json = jsonBuffer.parse((uint8_t *)(request->_tempObject));
if (json.success()) { if (json.success()) {
#elif ARDUINOJSON_VERSION_MAJOR == 6 #elif ARDUINOJSON_VERSION_MAJOR == 6
DynamicJsonDocument jsonBuffer(this->maxJsonBufferSize); DynamicJsonDocument jsonBuffer(this->maxJsonBufferSize);
DeserializationError error = deserializeJson(jsonBuffer, (uint8_t*)(request->_tempObject)); DeserializationError error = deserializeJson(jsonBuffer, (uint8_t *)(request->_tempObject));
if (!error) { if (!error) {
JsonVariant json = jsonBuffer.as<JsonVariant>(); JsonVariant json = jsonBuffer.as<JsonVariant>();
#else #else
JsonDocument jsonBuffer; JsonDocument jsonBuffer;
DeserializationError error = deserializeJson(jsonBuffer, (uint8_t*)(request->_tempObject)); DeserializationError error = deserializeJson(jsonBuffer, (uint8_t *)(request->_tempObject));
if (!error) { if (!error) {
JsonVariant json = jsonBuffer.as<JsonVariant>(); JsonVariant json = jsonBuffer.as<JsonVariant>();
#endif #endif
_onRequest(request, json); _onRequest(request, json);
return; 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) { if (_onRequest) {
_contentLength = total; _contentLength = total;
if (total > 0 && request->_tempObject == NULL && total < _maxContentLength) { if (total > 0 && request->_tempObject == NULL && total < _maxContentLength) {
request->_tempObject = malloc(total); request->_tempObject = malloc(total);
if (request->_tempObject == NULL) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
request->abort();
return;
}
} }
if (request->_tempObject != NULL) { 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 // SPDX-License-Identifier: LGPL-3.0-or-later
/* // Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
Async Response to use with ArduinoJson and AsyncWebServer
Written by Andrew Melvin (SticilFace) with help from me-no-dev and BBlanchon.
Example of callback in use
server.on("/json", HTTP_ANY, [](AsyncWebServerRequest * request) {
AsyncJsonResponse * response = new AsyncJsonResponse();
JsonObject& root = response->getRoot();
root["key1"] = "key number one";
JsonObject& nested = root.createNestedObject("nested");
nested["key1"] = "key number one";
response->setLength();
request->send(response);
});
--------------------
Async Request to use with ArduinoJson and AsyncWebServer
Written by Arsène von Wyss (avonwyss)
Example
AsyncCallbackJsonWebHandler* handler = new AsyncCallbackJsonWebHandler("/rest/endpoint");
handler->onRequest([](AsyncWebServerRequest *request, JsonVariant &json) {
JsonObject jsonObj = json.as<JsonObject>();
// ...
});
server.addHandler(handler);
*/
#ifndef ASYNC_JSON_H_ #ifndef ASYNC_JSON_H_
#define ASYNC_JSON_H_ #define ASYNC_JSON_H_
#if __has_include("ArduinoJson.h") #if __has_include("ArduinoJson.h")
#include <ArduinoJson.h> #include <ArduinoJson.h>
#if ARDUINOJSON_VERSION_MAJOR >= 5 #if ARDUINOJSON_VERSION_MAJOR >= 5
#define ASYNC_JSON_SUPPORT 1 #define ASYNC_JSON_SUPPORT 1
#else #else
#define ASYNC_JSON_SUPPORT 0 #define ASYNC_JSON_SUPPORT 0
#endif // ARDUINOJSON_VERSION_MAJOR >= 5 #endif // ARDUINOJSON_VERSION_MAJOR >= 5
#endif // __has_include("ArduinoJson.h") #endif // __has_include("ArduinoJson.h")
#if ASYNC_JSON_SUPPORT == 1 #if ASYNC_JSON_SUPPORT == 1
#include <ESPAsyncWebServer.h> #include <ESPAsyncWebServer.h>
#include "ChunkPrint.h" #include "ChunkPrint.h"
#if ARDUINOJSON_VERSION_MAJOR == 6 #if ARDUINOJSON_VERSION_MAJOR == 6
#ifndef DYNAMIC_JSON_DOCUMENT_SIZE #ifndef DYNAMIC_JSON_DOCUMENT_SIZE
#define DYNAMIC_JSON_DOCUMENT_SIZE 1024 #define DYNAMIC_JSON_DOCUMENT_SIZE 1024
#endif #endif
#endif #endif
class AsyncJsonResponse : public AsyncAbstractResponse { class AsyncJsonResponse : public AsyncAbstractResponse {
protected: protected:
#if ARDUINOJSON_VERSION_MAJOR == 5 #if ARDUINOJSON_VERSION_MAJOR == 5
DynamicJsonBuffer _jsonBuffer; DynamicJsonBuffer _jsonBuffer;
#elif ARDUINOJSON_VERSION_MAJOR == 6 #elif ARDUINOJSON_VERSION_MAJOR == 6
DynamicJsonDocument _jsonBuffer; DynamicJsonDocument _jsonBuffer;
#else #else
JsonDocument _jsonBuffer; JsonDocument _jsonBuffer;
#endif #endif
JsonVariant _root; JsonVariant _root;
bool _isValid; bool _isValid;
public: public:
#if ARDUINOJSON_VERSION_MAJOR == 6 #if ARDUINOJSON_VERSION_MAJOR == 6
AsyncJsonResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE); AsyncJsonResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE);
#else #else
AsyncJsonResponse(bool isArray = false); AsyncJsonResponse(bool isArray = false);
#endif #endif
JsonVariant& getRoot() { return _root; } JsonVariant &getRoot() {
bool _sourceValid() const { return _isValid; } return _root;
size_t setLength(); }
size_t getSize() const { return _jsonBuffer.size(); } bool _sourceValid() const {
size_t _fillBuffer(uint8_t* data, size_t len); return _isValid;
#if ARDUINOJSON_VERSION_MAJOR >= 6 }
bool overflowed() const { return _jsonBuffer.overflowed(); } size_t setLength();
#endif 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 { class PrettyAsyncJsonResponse : public AsyncJsonResponse {
public: public:
#if ARDUINOJSON_VERSION_MAJOR == 6 #if ARDUINOJSON_VERSION_MAJOR == 6
PrettyAsyncJsonResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE); PrettyAsyncJsonResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE);
#else #else
PrettyAsyncJsonResponse(bool isArray = false); PrettyAsyncJsonResponse(bool isArray = false);
#endif #endif
size_t setLength(); size_t setLength();
size_t _fillBuffer(uint8_t* data, size_t len); 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 { class AsyncCallbackJsonWebHandler : public AsyncWebHandler {
protected: protected:
String _uri; String _uri;
WebRequestMethodComposite _method; WebRequestMethodComposite _method;
ArJsonRequestHandlerFunction _onRequest; ArJsonRequestHandlerFunction _onRequest;
size_t _contentLength; size_t _contentLength;
#if ARDUINOJSON_VERSION_MAJOR == 6 #if ARDUINOJSON_VERSION_MAJOR == 6
size_t maxJsonBufferSize; size_t maxJsonBufferSize;
#endif #endif
size_t _maxContentLength; size_t _maxContentLength;
public: public:
#if ARDUINOJSON_VERSION_MAJOR == 6 #if ARDUINOJSON_VERSION_MAJOR == 6
AsyncCallbackJsonWebHandler(const String& uri, ArJsonRequestHandlerFunction onRequest = nullptr, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE); AsyncCallbackJsonWebHandler(const String &uri, ArJsonRequestHandlerFunction onRequest = nullptr, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE);
#else #else
AsyncCallbackJsonWebHandler(const String& uri, ArJsonRequestHandlerFunction onRequest = nullptr); AsyncCallbackJsonWebHandler(const String &uri, ArJsonRequestHandlerFunction onRequest = nullptr);
#endif #endif
void setMethod(WebRequestMethodComposite method) { _method = method; } void setMethod(WebRequestMethodComposite method) {
void setMaxContentLength(int maxContentLength) { _maxContentLength = maxContentLength; } _method = method;
void onRequest(ArJsonRequestHandlerFunction fn) { _onRequest = fn; } }
void setMaxContentLength(int maxContentLength) {
_maxContentLength = maxContentLength;
}
void onRequest(ArJsonRequestHandlerFunction fn) {
_onRequest = fn;
}
bool canHandle(AsyncWebServerRequest* request) const override final; bool canHandle(AsyncWebServerRequest *request) const override final;
void handleRequest(AsyncWebServerRequest* request) 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 handleUpload(
void handleBody(AsyncWebServerRequest* request, uint8_t* data, size_t len, size_t index, size_t total) override final; __unused AsyncWebServerRequest *request, __unused const String &filename, __unused size_t index, __unused uint8_t *data, __unused size_t len,
bool isRequestHandlerTrivial() const override final { return !_onRequest; } __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" #include "AsyncMessagePack.h"
#if ASYNC_MSG_PACK_SUPPORT == 1 #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} { AsyncMessagePackResponse::AsyncMessagePackResponse(bool isArray, size_t maxJsonBufferSize) : _jsonBuffer(maxJsonBufferSize), _isValid{false} {
_code = 200; _code = 200;
_contentType = asyncsrv::T_application_msgpack; _contentType = asyncsrv::T_application_msgpack;
if (isArray) if (isArray) {
_root = _jsonBuffer.createNestedArray(); _root = _jsonBuffer.createNestedArray();
else } else {
_root = _jsonBuffer.createNestedObject(); _root = _jsonBuffer.createNestedObject();
}
} }
#else #else
AsyncMessagePackResponse::AsyncMessagePackResponse(bool isArray) : _isValid{false} { AsyncMessagePackResponse::AsyncMessagePackResponse(bool isArray) : _isValid{false} {
_code = 200; _code = 200;
_contentType = asyncsrv::T_application_msgpack; _contentType = asyncsrv::T_application_msgpack;
if (isArray) if (isArray) {
_root = _jsonBuffer.add<JsonArray>(); _root = _jsonBuffer.add<JsonArray>();
else } else {
_root = _jsonBuffer.add<JsonObject>(); _root = _jsonBuffer.add<JsonObject>();
}
} }
#endif #endif
size_t AsyncMessagePackResponse::setLength() { size_t AsyncMessagePackResponse::setLength() {
_contentLength = measureMsgPack(_root); _contentLength = measureMsgPack(_root);
@ -30,34 +35,39 @@ size_t AsyncMessagePackResponse::setLength() {
return _contentLength; 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); ChunkPrint dest(data, _sentLength, len);
serializeMsgPack(_root, dest); serializeMsgPack(_root, dest);
return len; return len;
} }
#if ARDUINOJSON_VERSION_MAJOR == 6 #if ARDUINOJSON_VERSION_MAJOR == 6
AsyncCallbackMessagePackWebHandler::AsyncCallbackMessagePackWebHandler(const String& uri, ArMessagePackRequestHandlerFunction onRequest, size_t maxJsonBufferSize) AsyncCallbackMessagePackWebHandler::AsyncCallbackMessagePackWebHandler(
: _uri(uri), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), maxJsonBufferSize(maxJsonBufferSize), _maxContentLength(16384) {} const String &uri, ArMessagePackRequestHandlerFunction onRequest, size_t maxJsonBufferSize
#else )
AsyncCallbackMessagePackWebHandler::AsyncCallbackMessagePackWebHandler(const String& uri, ArMessagePackRequestHandlerFunction onRequest) : _uri(uri), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), maxJsonBufferSize(maxJsonBufferSize), _maxContentLength(16384) {}
: _uri(uri), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), _maxContentLength(16384) {} #else
#endif 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 { bool AsyncCallbackMessagePackWebHandler::canHandle(AsyncWebServerRequest *request) const {
if (!_onRequest || !request->isHTTP() || !(_method & request->method())) if (!_onRequest || !request->isHTTP() || !(_method & request->method())) {
return false; return false;
}
if (_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri + "/"))) if (_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri + "/"))) {
return false; 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 false;
}
return true; return true;
} }
void AsyncCallbackMessagePackWebHandler::handleRequest(AsyncWebServerRequest* request) { void AsyncCallbackMessagePackWebHandler::handleRequest(AsyncWebServerRequest *request) {
if (_onRequest) { if (_onRequest) {
if (request->method() == HTTP_GET) { if (request->method() == HTTP_GET) {
JsonVariant json; JsonVariant json;
@ -65,17 +75,17 @@ void AsyncCallbackMessagePackWebHandler::handleRequest(AsyncWebServerRequest* re
return; return;
} else if (request->_tempObject != NULL) { } else if (request->_tempObject != NULL) {
#if ARDUINOJSON_VERSION_MAJOR == 6 #if ARDUINOJSON_VERSION_MAJOR == 6
DynamicJsonDocument jsonBuffer(this->maxJsonBufferSize); DynamicJsonDocument jsonBuffer(this->maxJsonBufferSize);
DeserializationError error = deserializeMsgPack(jsonBuffer, (uint8_t*)(request->_tempObject)); DeserializationError error = deserializeMsgPack(jsonBuffer, (uint8_t *)(request->_tempObject));
if (!error) { if (!error) {
JsonVariant json = jsonBuffer.as<JsonVariant>(); JsonVariant json = jsonBuffer.as<JsonVariant>();
#else #else
JsonDocument jsonBuffer; JsonDocument jsonBuffer;
DeserializationError error = deserializeMsgPack(jsonBuffer, (uint8_t*)(request->_tempObject)); DeserializationError error = deserializeMsgPack(jsonBuffer, (uint8_t *)(request->_tempObject));
if (!error) { if (!error) {
JsonVariant json = jsonBuffer.as<JsonVariant>(); JsonVariant json = jsonBuffer.as<JsonVariant>();
#endif #endif
_onRequest(request, json); _onRequest(request, json);
return; 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) { if (_onRequest) {
_contentLength = total; _contentLength = total;
if (total > 0 && request->_tempObject == NULL && total < _maxContentLength) { if (total > 0 && request->_tempObject == NULL && total < _maxContentLength) {
request->_tempObject = malloc(total); request->_tempObject = malloc(total);
if (request->_tempObject == NULL) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
request->abort();
return;
}
} }
if (request->_tempObject != NULL) { 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 #pragma once
/* /*
@ -22,81 +25,102 @@
*/ */
#if __has_include("ArduinoJson.h") #if __has_include("ArduinoJson.h")
#include <ArduinoJson.h> #include <ArduinoJson.h>
#if ARDUINOJSON_VERSION_MAJOR >= 6 #if ARDUINOJSON_VERSION_MAJOR >= 6
#define ASYNC_MSG_PACK_SUPPORT 1 #define ASYNC_MSG_PACK_SUPPORT 1
#else #else
#define ASYNC_MSG_PACK_SUPPORT 0 #define ASYNC_MSG_PACK_SUPPORT 0
#endif // ARDUINOJSON_VERSION_MAJOR >= 6 #endif // ARDUINOJSON_VERSION_MAJOR >= 6
#endif // __has_include("ArduinoJson.h") #endif // __has_include("ArduinoJson.h")
#if ASYNC_MSG_PACK_SUPPORT == 1 #if ASYNC_MSG_PACK_SUPPORT == 1
#include <ESPAsyncWebServer.h> #include <ESPAsyncWebServer.h>
#include "ChunkPrint.h" #include "ChunkPrint.h"
#if ARDUINOJSON_VERSION_MAJOR == 6 #if ARDUINOJSON_VERSION_MAJOR == 6
#ifndef DYNAMIC_JSON_DOCUMENT_SIZE #ifndef DYNAMIC_JSON_DOCUMENT_SIZE
#define DYNAMIC_JSON_DOCUMENT_SIZE 1024 #define DYNAMIC_JSON_DOCUMENT_SIZE 1024
#endif #endif
#endif #endif
class AsyncMessagePackResponse : public AsyncAbstractResponse { class AsyncMessagePackResponse : public AsyncAbstractResponse {
protected: protected:
#if ARDUINOJSON_VERSION_MAJOR == 6 #if ARDUINOJSON_VERSION_MAJOR == 6
DynamicJsonDocument _jsonBuffer; DynamicJsonDocument _jsonBuffer;
#else #else
JsonDocument _jsonBuffer; JsonDocument _jsonBuffer;
#endif #endif
JsonVariant _root; JsonVariant _root;
bool _isValid; bool _isValid;
public: public:
#if ARDUINOJSON_VERSION_MAJOR == 6 #if ARDUINOJSON_VERSION_MAJOR == 6
AsyncMessagePackResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE); AsyncMessagePackResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE);
#else #else
AsyncMessagePackResponse(bool isArray = false); AsyncMessagePackResponse(bool isArray = false);
#endif #endif
JsonVariant& getRoot() { return _root; } JsonVariant &getRoot() {
bool _sourceValid() const { return _isValid; } return _root;
size_t setLength(); }
size_t getSize() const { return _jsonBuffer.size(); } bool _sourceValid() const {
size_t _fillBuffer(uint8_t* data, size_t len); return _isValid;
#if ARDUINOJSON_VERSION_MAJOR >= 6 }
bool overflowed() const { return _jsonBuffer.overflowed(); } size_t setLength();
#endif 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 { class AsyncCallbackMessagePackWebHandler : public AsyncWebHandler {
protected: protected:
String _uri; String _uri;
WebRequestMethodComposite _method; WebRequestMethodComposite _method;
ArMessagePackRequestHandlerFunction _onRequest; ArMessagePackRequestHandlerFunction _onRequest;
size_t _contentLength; size_t _contentLength;
#if ARDUINOJSON_VERSION_MAJOR == 6 #if ARDUINOJSON_VERSION_MAJOR == 6
size_t maxJsonBufferSize; size_t maxJsonBufferSize;
#endif #endif
size_t _maxContentLength; size_t _maxContentLength;
public: public:
#if ARDUINOJSON_VERSION_MAJOR == 6 #if ARDUINOJSON_VERSION_MAJOR == 6
AsyncCallbackMessagePackWebHandler(const String& uri, ArMessagePackRequestHandlerFunction onRequest = nullptr, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE); AsyncCallbackMessagePackWebHandler(
#else const String &uri, ArMessagePackRequestHandlerFunction onRequest = nullptr, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE
AsyncCallbackMessagePackWebHandler(const String& uri, ArMessagePackRequestHandlerFunction onRequest = nullptr); );
#endif #else
AsyncCallbackMessagePackWebHandler(const String &uri, ArMessagePackRequestHandlerFunction onRequest = nullptr);
#endif
void setMethod(WebRequestMethodComposite method) { _method = method; } void setMethod(WebRequestMethodComposite method) {
void setMaxContentLength(int maxContentLength) { _maxContentLength = maxContentLength; } _method = method;
void onRequest(ArMessagePackRequestHandlerFunction fn) { _onRequest = fn; } }
void setMaxContentLength(int maxContentLength) {
_maxContentLength = maxContentLength;
}
void onRequest(ArMessagePackRequestHandlerFunction fn) {
_onRequest = fn;
}
bool canHandle(AsyncWebServerRequest* request) const override final; bool canHandle(AsyncWebServerRequest *request) const override final;
void handleRequest(AsyncWebServerRequest* request) 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 handleUpload(
void handleBody(AsyncWebServerRequest* request, uint8_t* data, size_t len, size_t index, size_t total) override final; __unused AsyncWebServerRequest *request, __unused const String &filename, __unused size_t index, __unused uint8_t *data, __unused size_t len,
bool isRequestHandlerTrivial() const override final { return !_onRequest; } __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" #include "ESPAsyncWebServer.h"
AsyncWebHeader::AsyncWebHeader(const String& data) { AsyncWebHeader::AsyncWebHeader(const String &data) {
if (!data) if (!data) {
return; return;
}
int index = data.indexOf(':'); int index = data.indexOf(':');
if (index < 0) if (index < 0) {
return; return;
}
_name = data.substring(0, index); _name = data.substring(0, index);
_value = data.substring(index + 2); _value = data.substring(index + 2);
} }
String AsyncWebHeader::toString() const { String AsyncWebHeader::toString() const {
String str; String str;
str.reserve(_name.length() + _value.length() + 2); if (str.reserve(_name.length() + _value.length() + 2)) {
str.concat(_name); str.concat(_name);
str.concat((char)0x3a); str.concat((char)0x3a);
str.concat((char)0x20); str.concat((char)0x20);
str.concat(_value); str.concat(_value);
str.concat(asyncsrv::T_rn); str.concat(asyncsrv::T_rn);
} else {
#ifdef ESP32
log_e("Failed to allocate");
#endif
}
return str; 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 @@
/* // SPDX-License-Identifier: LGPL-3.0-or-later
Asynchronous WebServer library for Espressif MCUs // 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_ #ifndef ASYNCWEBSOCKET_H_
#define ASYNCWEBSOCKET_H_ #define ASYNCWEBSOCKET_H_
#include <Arduino.h> #include <Arduino.h>
#ifdef ESP32 #ifdef ESP32
#include "../../ESP32Async-AsyncTCP/src/AsyncTCP.h" #include "../../mathieucarbou-AsyncTCPSock/src/AsyncTCP.h"
#include <mutex> #include <mutex>
#ifndef WS_MAX_QUEUED_MESSAGES #ifndef WS_MAX_QUEUED_MESSAGES
#define WS_MAX_QUEUED_MESSAGES 32 #define WS_MAX_QUEUED_MESSAGES 32
#endif #endif
#elif defined(ESP8266) #elif defined(ESP8266)
#include <ESPAsyncTCP.h> #include <ESPAsyncTCP.h>
#ifndef WS_MAX_QUEUED_MESSAGES #ifndef WS_MAX_QUEUED_MESSAGES
#define WS_MAX_QUEUED_MESSAGES 8 #define WS_MAX_QUEUED_MESSAGES 8
#endif #endif
#elif defined(TARGET_RP2040) #elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <AsyncTCP_RP2040W.h> #include <RPAsyncTCP.h>
#ifndef WS_MAX_QUEUED_MESSAGES #ifndef WS_MAX_QUEUED_MESSAGES
#define WS_MAX_QUEUED_MESSAGES 32 #define WS_MAX_QUEUED_MESSAGES 32
#endif #endif
#endif #endif
#include "ESPAsyncWebServer.h" #include "ESPAsyncWebServer.h"
@ -45,18 +28,18 @@
#include <memory> #include <memory>
#ifdef ESP8266 #ifdef ESP8266
#include <Hash.h> #include <Hash.h>
#ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library #ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library
#include <../src/Hash.h> #include <../src/Hash.h>
#endif #endif
#endif #endif
#ifndef DEFAULT_MAX_WS_CLIENTS #ifndef DEFAULT_MAX_WS_CLIENTS
#ifdef ESP32 #ifdef ESP32
#define DEFAULT_MAX_WS_CLIENTS 8 #define DEFAULT_MAX_WS_CLIENTS 8
#else #else
#define DEFAULT_MAX_WS_CLIENTS 4 #define DEFAULT_MAX_WS_CLIENTS 4
#endif #endif
#endif #endif
using AsyncWebSocketSharedBuffer = std::shared_ptr<std::vector<uint8_t>>; using AsyncWebSocketSharedBuffer = std::shared_ptr<std::vector<uint8_t>>;
@ -67,311 +50,367 @@ class AsyncWebSocketClient;
class AsyncWebSocketControl; class AsyncWebSocketControl;
typedef struct { 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. * Note: Applications will only see WS_TEXT and WS_BINARY.
* All other types are handled by the library. */ * All other types are handled by the library. */
uint8_t message_opcode; uint8_t message_opcode;
/** Frame number of a fragmented message. */ /** Frame number of a fragmented message. */
uint32_t num; uint32_t num;
/** Is this the last frame in a fragmented message ?*/ /** Is this the last frame in a fragmented message ?*/
uint8_t final; uint8_t final;
/** Is this frame masked? */ /** Is this frame masked? */
uint8_t masked; uint8_t masked;
/** Message type as defined by enum AwsFrameType. /** Message type as defined by enum AwsFrameType.
* This value is the same as message_opcode for non-fragmented * This value is the same as message_opcode for non-fragmented
* messages, but may also be WS_CONTINUATION in a fragmented message. */ * messages, but may also be WS_CONTINUATION in a fragmented message. */
uint8_t opcode; uint8_t opcode;
/** Length of the current frame. /** Length of the current frame.
* This equals the total length of the message if num == 0 && final == true */ * This equals the total length of the message if num == 0 && final == true */
uint64_t len; uint64_t len;
/** Mask key */ /** Mask key */
uint8_t mask[4]; uint8_t mask[4];
/** Offset of the data inside the current frame. */ /** Offset of the data inside the current frame. */
uint64_t index; uint64_t index;
} AwsFrameInfo; } AwsFrameInfo;
typedef enum { WS_DISCONNECTED, typedef enum {
WS_CONNECTED, WS_DISCONNECTED,
WS_DISCONNECTING } AwsClientStatus; WS_CONNECTED,
typedef enum { WS_CONTINUATION, WS_DISCONNECTING
WS_TEXT, } AwsClientStatus;
WS_BINARY, typedef enum {
WS_DISCONNECT = 0x08, WS_CONTINUATION,
WS_PING, WS_TEXT,
WS_PONG } AwsFrameType; WS_BINARY,
typedef enum { WS_MSG_SENDING, WS_DISCONNECT = 0x08,
WS_MSG_SENT, WS_PING,
WS_MSG_ERROR } AwsMessageStatus; WS_PONG
typedef enum { WS_EVT_CONNECT, } AwsFrameType;
WS_EVT_DISCONNECT, typedef enum {
WS_EVT_PING, WS_MSG_SENDING,
WS_EVT_PONG, WS_MSG_SENT,
WS_EVT_ERROR, WS_MSG_ERROR
WS_EVT_DATA } AwsEventType; } AwsMessageStatus;
typedef enum {
WS_EVT_CONNECT,
WS_EVT_DISCONNECT,
WS_EVT_PING,
WS_EVT_PONG,
WS_EVT_ERROR,
WS_EVT_DATA
} AwsEventType;
class AsyncWebSocketMessageBuffer { class AsyncWebSocketMessageBuffer {
friend AsyncWebSocket; friend AsyncWebSocket;
friend AsyncWebSocketClient; friend AsyncWebSocketClient;
private: private:
AsyncWebSocketSharedBuffer _buffer; AsyncWebSocketSharedBuffer _buffer;
public: public:
AsyncWebSocketMessageBuffer() {} AsyncWebSocketMessageBuffer() {}
explicit AsyncWebSocketMessageBuffer(size_t size); explicit AsyncWebSocketMessageBuffer(size_t size);
AsyncWebSocketMessageBuffer(const uint8_t* data, size_t size); AsyncWebSocketMessageBuffer(const uint8_t *data, size_t size);
//~AsyncWebSocketMessageBuffer(); //~AsyncWebSocketMessageBuffer();
bool reserve(size_t size); bool reserve(size_t size);
uint8_t* get() { return _buffer->data(); } uint8_t *get() {
size_t length() const { return _buffer->size(); } return _buffer->data();
}
size_t length() const {
return _buffer->size();
}
}; };
class AsyncWebSocketMessage { class AsyncWebSocketMessage {
private: private:
AsyncWebSocketSharedBuffer _WSbuffer; AsyncWebSocketSharedBuffer _WSbuffer;
uint8_t _opcode{WS_TEXT}; uint8_t _opcode{WS_TEXT};
bool _mask{false}; bool _mask{false};
AwsMessageStatus _status{WS_MSG_ERROR}; AwsMessageStatus _status{WS_MSG_ERROR};
size_t _sent{}; size_t _sent{};
size_t _ack{}; size_t _ack{};
size_t _acked{}; size_t _acked{};
public: public:
AsyncWebSocketMessage(AsyncWebSocketSharedBuffer buffer, uint8_t opcode = WS_TEXT, bool mask = false); AsyncWebSocketMessage(AsyncWebSocketSharedBuffer buffer, uint8_t opcode = WS_TEXT, bool mask = false);
bool finished() const { return _status != WS_MSG_SENDING; } bool finished() const {
bool betweenFrames() const { return _acked == _ack; } return _status != WS_MSG_SENDING;
}
bool betweenFrames() const {
return _acked == _ack;
}
void ack(size_t len, uint32_t time); void ack(size_t len, uint32_t time);
size_t send(AsyncClient* client); size_t send(AsyncClient *client);
}; };
class AsyncWebSocketClient { class AsyncWebSocketClient {
private: private:
AsyncClient* _client; AsyncClient *_client;
AsyncWebSocket* _server; AsyncWebSocket *_server;
uint32_t _clientId; uint32_t _clientId;
AwsClientStatus _status; AwsClientStatus _status;
#ifdef ESP32 #ifdef ESP32
mutable std::mutex _lock; mutable std::mutex _lock;
#endif #endif
std::deque<AsyncWebSocketControl> _controlQueue; std::deque<AsyncWebSocketControl> _controlQueue;
std::deque<AsyncWebSocketMessage> _messageQueue; std::deque<AsyncWebSocketMessage> _messageQueue;
bool closeWhenFull = true; bool closeWhenFull = true;
uint8_t _pstate; uint8_t _pstate;
AwsFrameInfo _pinfo; AwsFrameInfo _pinfo;
uint32_t _lastMessageTime; uint32_t _lastMessageTime;
uint32_t _keepAlivePeriod; uint32_t _keepAlivePeriod;
bool _queueControl(uint8_t opcode, const uint8_t* data = NULL, size_t len = 0, bool mask = false); 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); bool _queueMessage(AsyncWebSocketSharedBuffer buffer, uint8_t opcode = WS_TEXT, bool mask = false);
void _runQueue(); void _runQueue();
void _clearQueue(); void _clearQueue();
public: public:
void* _tempObject; void *_tempObject;
AsyncWebSocketClient(AsyncWebServerRequest* request, AsyncWebSocket* server); AsyncWebSocketClient(AsyncWebServerRequest *request, AsyncWebSocket *server);
~AsyncWebSocketClient(); ~AsyncWebSocketClient();
// client id increments for the given server // client id increments for the given server
uint32_t id() const { return _clientId; } uint32_t id() const {
AwsClientStatus status() const { return _status; } return _clientId;
AsyncClient* client() { return _client; } }
const AsyncClient* client() const { return _client; } AwsClientStatus status() const {
AsyncWebSocket* server() { return _server; } return _status;
const AsyncWebSocket* server() const { return _server; } }
AwsFrameInfo const& pinfo() const { return _pinfo; } 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. // - 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. // 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, // 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. // and so on, causing a resource exhaustion.
// //
// - If "false", the incoming message will be discarded if the queue is full. // - 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 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). // 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. // - 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. // - IT is recommended to use the methods queueIsFull(), availableForWriteAll(), availableForWrite(clientId) to check if the queue is full before sending a message.
// //
// Usage: // Usage:
// - can be set in the onEvent listener when connecting (event type is: WS_EVT_CONNECT) // - can be set in the onEvent listener when connecting (event type is: WS_EVT_CONNECT)
// //
// Use cases:, // Use cases:,
// - if using websocket to send logging messages, maybe some loss is acceptable. // - 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. // - 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; } void setCloseClientOnQueueFull(bool close) {
bool willCloseClientOnQueueFull() const { return closeWhenFull; } closeWhenFull = close;
}
bool willCloseClientOnQueueFull() const {
return closeWhenFull;
}
IPAddress remoteIP() const; IPAddress remoteIP() const;
uint16_t remotePort() const; uint16_t remotePort() const;
bool shouldBeDeleted() const { return !_client; } bool shouldBeDeleted() const {
return !_client;
}
// control frames // control frames
void close(uint16_t code = 0, const char* message = NULL); void close(uint16_t code = 0, const char *message = NULL);
bool ping(const uint8_t* data = NULL, size_t len = 0); bool ping(const uint8_t *data = NULL, size_t len = 0);
// set auto-ping period in seconds. disabled if zero (default) // set auto-ping period in seconds. disabled if zero (default)
void keepAlivePeriod(uint16_t seconds) { void keepAlivePeriod(uint16_t seconds) {
_keepAlivePeriod = seconds * 1000; _keepAlivePeriod = seconds * 1000;
} }
uint16_t keepAlivePeriod() { uint16_t keepAlivePeriod() {
return (uint16_t)(_keepAlivePeriod / 1000); return (uint16_t)(_keepAlivePeriod / 1000);
} }
// data packets // data packets
void message(AsyncWebSocketSharedBuffer buffer, uint8_t opcode = WS_TEXT, bool mask = false) { _queueMessage(buffer, opcode, mask); } void message(AsyncWebSocketSharedBuffer buffer, uint8_t opcode = WS_TEXT, bool mask = false) {
bool queueIsFull() const; _queueMessage(buffer, opcode, mask);
size_t queueLen() const; }
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(AsyncWebSocketSharedBuffer buffer);
bool text(const uint8_t* message, size_t len); bool text(const uint8_t *message, size_t len);
bool text(const char* message, size_t len); bool text(const char *message, size_t len);
bool text(const char* message); bool text(const char *message);
bool text(const String& message); bool text(const String &message);
bool text(AsyncWebSocketMessageBuffer* buffer); bool text(AsyncWebSocketMessageBuffer *buffer);
bool binary(AsyncWebSocketSharedBuffer buffer); bool binary(AsyncWebSocketSharedBuffer buffer);
bool binary(const uint8_t* message, size_t len); bool binary(const uint8_t *message, size_t len);
bool binary(const char* message, size_t len); bool binary(const char *message, size_t len);
bool binary(const char* message); bool binary(const char *message);
bool binary(const String& message); bool binary(const String &message);
bool binary(AsyncWebSocketMessageBuffer* buffer); bool binary(AsyncWebSocketMessageBuffer *buffer);
bool canSend() const; bool canSend() const;
// system callbacks (do not call) // system callbacks (do not call)
void _onAck(size_t len, uint32_t time); void _onAck(size_t len, uint32_t time);
void _onError(int8_t); void _onError(int8_t);
void _onPoll(); void _onPoll();
void _onTimeout(uint32_t time); void _onTimeout(uint32_t time);
void _onDisconnect(); void _onDisconnect();
void _onData(void* pbuf, size_t plen); void _onData(void *pbuf, size_t plen);
#ifdef ESP8266 #ifdef ESP8266
size_t printf_P(PGM_P formatP, ...) __attribute__((format(printf, 2, 3))); size_t printf_P(PGM_P formatP, ...) __attribute__((format(printf, 2, 3)));
bool text(const __FlashStringHelper* message); bool text(const __FlashStringHelper *message);
bool binary(const __FlashStringHelper* message, size_t len); bool binary(const __FlashStringHelper *message, size_t len);
#endif #endif
}; };
using AwsHandshakeHandler = std::function<bool(AsyncWebServerRequest* request)>; 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 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 // WebServer Handler implementation that plays the role of a socket server
class AsyncWebSocket : public AsyncWebHandler { class AsyncWebSocket : public AsyncWebHandler {
private: private:
String _url; String _url;
std::list<AsyncWebSocketClient> _clients; std::list<AsyncWebSocketClient> _clients;
uint32_t _cNextId; uint32_t _cNextId;
AwsEventHandler _eventHandler{nullptr}; AwsEventHandler _eventHandler{nullptr};
AwsHandshakeHandler _handshakeHandler; AwsHandshakeHandler _handshakeHandler;
bool _enabled; bool _enabled;
#ifdef ESP32 #ifdef ESP32
mutable std::mutex _lock; mutable std::mutex _lock;
#endif #endif
public: public:
typedef enum { typedef enum {
DISCARDED = 0, DISCARDED = 0,
ENQUEUED = 1, ENQUEUED = 1,
PARTIALLY_ENQUEUED = 2, PARTIALLY_ENQUEUED = 2,
} SendStatus; } SendStatus;
explicit AsyncWebSocket(const char* url) : _url(url), _cNextId(1), _enabled(true) {} explicit AsyncWebSocket(const char *url) : _url(url), _cNextId(1), _enabled(true) {}
AsyncWebSocket(const String& url) : _url(url), _cNextId(1), _enabled(true) {} AsyncWebSocket(const String &url) : _url(url), _cNextId(1), _enabled(true) {}
~AsyncWebSocket() {}; ~AsyncWebSocket(){};
const char* url() const { return _url.c_str(); } const char *url() const {
void enable(bool e) { _enabled = e; } return _url.c_str();
bool enabled() const { return _enabled; } }
bool availableForWriteAll(); void enable(bool e) {
bool availableForWrite(uint32_t id); _enabled = e;
}
bool enabled() const {
return _enabled;
}
bool availableForWriteAll();
bool availableForWrite(uint32_t id);
size_t count() const; size_t count() const;
AsyncWebSocketClient* client(uint32_t id); AsyncWebSocketClient *client(uint32_t id);
bool hasClient(uint32_t id) { return client(id) != nullptr; } bool hasClient(uint32_t id) {
return client(id) != nullptr;
}
void close(uint32_t id, uint16_t code = 0, const char* message = NULL); void close(uint32_t id, uint16_t code = 0, const char *message = NULL);
void closeAll(uint16_t code = 0, const char* message = NULL); void closeAll(uint16_t code = 0, const char *message = NULL);
void cleanupClients(uint16_t maxClients = DEFAULT_MAX_WS_CLIENTS); void cleanupClients(uint16_t maxClients = DEFAULT_MAX_WS_CLIENTS);
bool ping(uint32_t id, const uint8_t* data = NULL, size_t len = 0); 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 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 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, size_t len);
bool text(uint32_t id, const char* message); bool text(uint32_t id, const char *message);
bool text(uint32_t id, const String& message); bool text(uint32_t id, const String &message);
bool text(uint32_t id, AsyncWebSocketMessageBuffer* buffer); bool text(uint32_t id, AsyncWebSocketMessageBuffer *buffer);
bool text(uint32_t id, AsyncWebSocketSharedBuffer buffer); bool text(uint32_t id, AsyncWebSocketSharedBuffer buffer);
SendStatus textAll(const uint8_t* message, size_t len); SendStatus textAll(const uint8_t *message, size_t len);
SendStatus textAll(const char* message, size_t len); SendStatus textAll(const char *message, size_t len);
SendStatus textAll(const char* message); SendStatus textAll(const char *message);
SendStatus textAll(const String& message); SendStatus textAll(const String &message);
SendStatus textAll(AsyncWebSocketMessageBuffer* buffer); SendStatus textAll(AsyncWebSocketMessageBuffer *buffer);
SendStatus textAll(AsyncWebSocketSharedBuffer buffer); SendStatus textAll(AsyncWebSocketSharedBuffer buffer);
bool binary(uint32_t id, const uint8_t* message, size_t len); 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, size_t len);
bool binary(uint32_t id, const char* message); bool binary(uint32_t id, const char *message);
bool binary(uint32_t id, const String& message); bool binary(uint32_t id, const String &message);
bool binary(uint32_t id, AsyncWebSocketMessageBuffer* buffer); bool binary(uint32_t id, AsyncWebSocketMessageBuffer *buffer);
bool binary(uint32_t id, AsyncWebSocketSharedBuffer buffer); bool binary(uint32_t id, AsyncWebSocketSharedBuffer buffer);
SendStatus binaryAll(const uint8_t* message, size_t len); SendStatus binaryAll(const uint8_t *message, size_t len);
SendStatus binaryAll(const char* message, size_t len); SendStatus binaryAll(const char *message, size_t len);
SendStatus binaryAll(const char* message); SendStatus binaryAll(const char *message);
SendStatus binaryAll(const String& message); SendStatus binaryAll(const String &message);
SendStatus binaryAll(AsyncWebSocketMessageBuffer* buffer); SendStatus binaryAll(AsyncWebSocketMessageBuffer *buffer);
SendStatus binaryAll(AsyncWebSocketSharedBuffer buffer); SendStatus binaryAll(AsyncWebSocketSharedBuffer buffer);
size_t printf(uint32_t id, const char* format, ...) __attribute__((format(printf, 3, 4))); 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 printfAll(const char *format, ...) __attribute__((format(printf, 2, 3)));
#ifdef ESP8266 #ifdef ESP8266
bool text(uint32_t id, const __FlashStringHelper* message); bool text(uint32_t id, const __FlashStringHelper *message);
SendStatus textAll(const __FlashStringHelper* message); SendStatus textAll(const __FlashStringHelper *message);
bool binary(uint32_t id, const __FlashStringHelper* message, size_t len); bool binary(uint32_t id, const __FlashStringHelper *message, size_t len);
SendStatus binaryAll(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 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))); size_t printfAll_P(PGM_P formatP, ...) __attribute__((format(printf, 2, 3)));
#endif #endif
void onEvent(AwsEventHandler handler) { _eventHandler = handler; } void onEvent(AwsEventHandler handler) {
void handleHandshake(AwsHandshakeHandler handler) { _handshakeHandler = handler; } _eventHandler = handler;
}
void handleHandshake(AwsHandshakeHandler handler) {
_handshakeHandler = handler;
}
// system callbacks (do not call) // system callbacks (do not call)
uint32_t _getNextId() { return _cNextId++; } uint32_t _getNextId() {
AsyncWebSocketClient* _newClient(AsyncWebServerRequest* request); return _cNextId++;
void _handleEvent(AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len); }
bool canHandle(AsyncWebServerRequest* request) const override final; AsyncWebSocketClient *_newClient(AsyncWebServerRequest *request);
void handleRequest(AsyncWebServerRequest* request) override final; 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. // messagebuffer functions/objects.
AsyncWebSocketMessageBuffer* makeBuffer(size_t size = 0); AsyncWebSocketMessageBuffer *makeBuffer(size_t size = 0);
AsyncWebSocketMessageBuffer* makeBuffer(const uint8_t* data, size_t size); 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 // WebServer response to authenticate the socket and detach the tcp client from the web server request
class AsyncWebSocketResponse : public AsyncWebServerResponse { class AsyncWebSocketResponse : public AsyncWebServerResponse {
private: private:
String _content; String _content;
AsyncWebSocket* _server; AsyncWebSocket *_server;
public: public:
AsyncWebSocketResponse(const String& key, AsyncWebSocket* server); AsyncWebSocketResponse(const String &key, AsyncWebSocket *server);
void _respond(AsyncWebServerRequest* request); void _respond(AsyncWebServerRequest *request);
size_t _ack(AsyncWebServerRequest* request, size_t len, uint32_t time); size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time);
bool _sourceValid() const { return true; } bool _sourceValid() const {
return true;
}
}; };
#endif /* ASYNCWEBSOCKET_H_ */ #endif /* ASYNCWEBSOCKET_H_ */

View file

@ -281,4 +281,4 @@ void SHA1Builder::getBytes(uint8_t *output) {
memcpy(output, hash, SHA1_HASH_SIZE); 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 #define SHA1_HASH_SIZE 20
class SHA1Builder { class SHA1Builder {
private: private:
uint32_t total[2]; /* number of bytes processed */ uint32_t total[2]; /* number of bytes processed */
uint32_t state[5]; /* intermediate digest state */ uint32_t state[5]; /* intermediate digest state */
unsigned char buffer[64]; /* data block being processed */ unsigned char buffer[64]; /* data block being processed */
uint8_t hash[SHA1_HASH_SIZE]; /* SHA-1 result */ uint8_t hash[SHA1_HASH_SIZE]; /* SHA-1 result */
void process(const uint8_t* data); void process(const uint8_t *data);
public: public:
void begin(); void begin();
void add(const uint8_t* data, size_t len); void add(const uint8_t *data, size_t len);
void calculate(); void calculate();
void getBytes(uint8_t* output); 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" #include "ChunkPrint.h"
ChunkPrint::ChunkPrint(uint8_t* destination, size_t from, size_t len) ChunkPrint::ChunkPrint(uint8_t *destination, size_t from, size_t len) : _destination(destination), _to_skip(from), _to_write(len), _pos{0} {}
: _destination(destination), _to_skip(from), _to_write(len), _pos{0} {}
size_t ChunkPrint::write(uint8_t c) { size_t ChunkPrint::write(uint8_t c) {
if (_to_skip > 0) { if (_to_skip > 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 #ifndef CHUNKPRINT_H
#define CHUNKPRINT_H #define CHUNKPRINT_H
#include <Print.h> #include <Print.h>
class ChunkPrint : public Print { class ChunkPrint : public Print {
private: private:
uint8_t* _destination; uint8_t *_destination;
size_t _to_skip; size_t _to_skip;
size_t _to_write; size_t _to_write;
size_t _pos; size_t _pos;
public: public:
ChunkPrint(uint8_t* destination, size_t from, size_t len); ChunkPrint(uint8_t *destination, size_t from, size_t len);
size_t write(uint8_t c); size_t write(uint8_t c);
size_t write(const uint8_t* buffer, size_t size) { return this->Print::write(buffer, size); } size_t write(const uint8_t *buffer, size_t size) {
return this->Print::write(buffer, size);
}
}; };
#endif #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 "WebAuthentication.h"
#include "ESPAsyncWebServer.h" #include "ESPAsyncWebServer.h"
AsyncMiddlewareChain::~AsyncMiddlewareChain() { AsyncMiddlewareChain::~AsyncMiddlewareChain() {
for (AsyncMiddleware* m : _middlewares) for (AsyncMiddleware *m : _middlewares) {
if (m->_freeOnRemoval) if (m->_freeOnRemoval) {
delete m; delete m;
}
}
} }
void AsyncMiddlewareChain::addMiddleware(ArMiddlewareCallback fn) { void AsyncMiddlewareChain::addMiddleware(ArMiddlewareCallback fn) {
AsyncMiddlewareFunction* m = new AsyncMiddlewareFunction(fn); AsyncMiddlewareFunction *m = new AsyncMiddlewareFunction(fn);
m->_freeOnRemoval = true; m->_freeOnRemoval = true;
_middlewares.emplace_back(m); _middlewares.emplace_back(m);
} }
void AsyncMiddlewareChain::addMiddleware(AsyncMiddleware* middleware) { void AsyncMiddlewareChain::addMiddleware(AsyncMiddleware *middleware) {
if (middleware) if (middleware) {
_middlewares.emplace_back(middleware); _middlewares.emplace_back(middleware);
}
} }
void AsyncMiddlewareChain::addMiddlewares(std::vector<AsyncMiddleware*> middlewares) { void AsyncMiddlewareChain::addMiddlewares(std::vector<AsyncMiddleware *> middlewares) {
for (AsyncMiddleware* m : middlewares) for (AsyncMiddleware *m : middlewares) {
addMiddleware(m); 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. // 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(); const size_t size = _middlewares.size();
_middlewares.erase(std::remove_if(_middlewares.begin(), _middlewares.end(), [middleware](AsyncMiddleware* m) { _middlewares.erase(
if (m == middleware) { std::remove_if(
if (m->_freeOnRemoval) _middlewares.begin(), _middlewares.end(),
delete m; [middleware](AsyncMiddleware *m) {
return true; if (m == middleware) {
} if (m->_freeOnRemoval) {
return false; delete m;
}), }
_middlewares.end()); return true;
}
return false;
}
),
_middlewares.end()
);
return size != _middlewares.size(); return size != _middlewares.size();
} }
void AsyncMiddlewareChain::_runChain(AsyncWebServerRequest* request, ArMiddlewareNext finalizer) { void AsyncMiddlewareChain::_runChain(AsyncWebServerRequest *request, ArMiddlewareNext finalizer) {
if (!_middlewares.size()) if (!_middlewares.size()) {
return finalizer(); return finalizer();
}
ArMiddlewareNext next; ArMiddlewareNext next;
std::list<AsyncMiddleware*>::iterator it = _middlewares.begin(); std::list<AsyncMiddleware *>::iterator it = _middlewares.begin();
next = [this, &next, &it, request, finalizer]() { next = [this, &next, &it, request, finalizer]() {
if (it == _middlewares.end()) if (it == _middlewares.end()) {
return finalizer(); return finalizer();
AsyncMiddleware* m = *it; }
AsyncMiddleware *m = *it;
it++; it++;
return m->run(request, next); return m->run(request, next);
}; };
return next(); return next();
} }
void AsyncAuthenticationMiddleware::setUsername(const char* username) { void AsyncAuthenticationMiddleware::setUsername(const char *username) {
_username = username; _username = username;
_hasCreds = _username.length() && _credentials.length(); _hasCreds = _username.length() && _credentials.length();
} }
void AsyncAuthenticationMiddleware::setPassword(const char* password) { void AsyncAuthenticationMiddleware::setPassword(const char *password) {
_credentials = password; _credentials = password;
_hash = false; _hash = false;
_hasCreds = _username.length() && _credentials.length(); _hasCreds = _username.length() && _credentials.length();
} }
void AsyncAuthenticationMiddleware::setPasswordHash(const char* hash) { void AsyncAuthenticationMiddleware::setPasswordHash(const char *hash) {
_credentials = hash; _credentials = hash;
_hash = _credentials.length(); _hash = _credentials.length();
_hasCreds = _username.length() && _credentials.length(); _hasCreds = _username.length() && _credentials.length();
@ -72,71 +87,86 @@ void AsyncAuthenticationMiddleware::setPasswordHash(const char* hash) {
bool AsyncAuthenticationMiddleware::generateHash() { bool AsyncAuthenticationMiddleware::generateHash() {
// ensure we have all the necessary data // ensure we have all the necessary data
if (!_hasCreds) if (!_hasCreds) {
return false; return false;
}
// if we already have a hash, do nothing // if we already have a hash, do nothing
if (_hash) if (_hash) {
return false; return false;
}
switch (_authMethod) { switch (_authMethod) {
case AsyncAuthType::AUTH_DIGEST: case AsyncAuthType::AUTH_DIGEST:
_credentials = generateDigestHash(_username.c_str(), _credentials.c_str(), _realm.c_str()); _credentials = generateDigestHash(_username.c_str(), _credentials.c_str(), _realm.c_str());
_hash = true; if (_credentials.length()) {
return true; _hash = true;
return true;
} else {
return false;
}
case AsyncAuthType::AUTH_BASIC: case AsyncAuthType::AUTH_BASIC:
_credentials = generateBasicHash(_username.c_str(), _credentials.c_str()); _credentials = generateBasicHash(_username.c_str(), _credentials.c_str());
_hash = true; if (_credentials.length()) {
return true; _hash = true;
return true;
} else {
return false;
}
default: default: return false;
return false;
} }
} }
bool AsyncAuthenticationMiddleware::allowed(AsyncWebServerRequest* request) const { bool AsyncAuthenticationMiddleware::allowed(AsyncWebServerRequest *request) const {
if (_authMethod == AsyncAuthType::AUTH_NONE) if (_authMethod == AsyncAuthType::AUTH_NONE) {
return true; return true;
}
if (_authMethod == AsyncAuthType::AUTH_DENIED) if (_authMethod == AsyncAuthType::AUTH_DENIED) {
return false; return false;
}
if (!_hasCreds) if (!_hasCreds) {
return true; return true;
}
return request->authenticate(_username.c_str(), _credentials.c_str(), _realm.c_str(), _hash); 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()); return allowed(request) ? next() : request->requestAuthentication(_authMethod, _realm.c_str(), _authFailMsg.c_str());
} }
void AsyncHeaderFreeMiddleware::run(AsyncWebServerRequest* request, ArMiddlewareNext next) { void AsyncHeaderFreeMiddleware::run(AsyncWebServerRequest *request, ArMiddlewareNext next) {
std::vector<const char*> reqHeaders; std::list<const char *> toRemove;
request->getHeaderNames(reqHeaders); for (auto &h : request->getHeaders()) {
for (const char* h : reqHeaders) {
bool keep = false; bool keep = false;
for (const char* k : _toKeep) { for (const char *k : _toKeep) {
if (strcasecmp(h, k) == 0) { if (strcasecmp(h.name().c_str(), k) == 0) {
keep = true; keep = true;
break; break;
} }
} }
if (!keep) { if (!keep) {
request->removeHeader(h); toRemove.push_back(h.name().c_str());
} }
} }
for (const char *h : toRemove) {
request->removeHeader(h);
}
next(); next();
} }
void AsyncHeaderFilterMiddleware::run(AsyncWebServerRequest* request, ArMiddlewareNext next) { void AsyncHeaderFilterMiddleware::run(AsyncWebServerRequest *request, ArMiddlewareNext next) {
for (auto it = _toRemove.begin(); it != _toRemove.end(); ++it) for (auto it = _toRemove.begin(); it != _toRemove.end(); ++it) {
request->removeHeader(*it); request->removeHeader(*it);
}
next(); next();
} }
void AsyncLoggingMiddleware::run(AsyncWebServerRequest* request, ArMiddlewareNext next) { void AsyncLoggingMiddleware::run(AsyncWebServerRequest *request, ArMiddlewareNext next) {
if (!isEnabled()) { if (!isEnabled()) {
next(); next();
return; return;
@ -152,7 +182,7 @@ void AsyncLoggingMiddleware::run(AsyncWebServerRequest* request, ArMiddlewareNex
_out->print(request->url().c_str()); _out->print(request->url().c_str());
_out->print(F(" HTTP/1.")); _out->print(F(" HTTP/1."));
_out->println(request->version()); _out->println(request->version());
for (auto& h : request->getHeaders()) { for (auto &h : request->getHeaders()) {
if (h.value().length()) { if (h.value().length()) {
_out->print('>'); _out->print('>');
_out->print(' '); _out->print(' ');
@ -166,7 +196,7 @@ void AsyncLoggingMiddleware::run(AsyncWebServerRequest* request, ArMiddlewareNex
uint32_t elapsed = millis(); uint32_t elapsed = millis();
next(); next();
elapsed = millis() - elapsed; elapsed = millis() - elapsed;
AsyncWebServerResponse* response = request->getResponse(); AsyncWebServerResponse *response = request->getResponse();
if (response) { if (response) {
_out->print(F("* Processed in ")); _out->print(F("* Processed in "));
_out->print(elapsed); _out->print(elapsed);
@ -178,7 +208,7 @@ void AsyncLoggingMiddleware::run(AsyncWebServerRequest* request, ArMiddlewareNex
_out->print(response->code()); _out->print(response->code());
_out->print(' '); _out->print(' ');
_out->println(AsyncWebServerResponse::responseCodeToString(response->code())); _out->println(AsyncWebServerResponse::responseCodeToString(response->code()));
for (auto& h : response->getHeaders()) { for (auto &h : response->getHeaders()) {
if (h.value().length()) { if (h.value().length()) {
_out->print('<'); _out->print('<');
_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_ACAO, _origin.c_str());
response->addHeader(asyncsrv::T_CORS_ACAM, _methods.c_str()); response->addHeader(asyncsrv::T_CORS_ACAM, _methods.c_str());
response->addHeader(asyncsrv::T_CORS_ACAH, _headers.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()); 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 // Origin header ? => CORS handling
if (request->hasHeader(asyncsrv::T_CORS_O)) { if (request->hasHeader(asyncsrv::T_CORS_O)) {
// check if this is a preflight request => handle it and return // check if this is a preflight request => handle it and return
if (request->method() == HTTP_OPTIONS) { if (request->method() == HTTP_OPTIONS) {
AsyncWebServerResponse* response = request->beginResponse(200); AsyncWebServerResponse *response = request->beginResponse(200);
addCORSHeaders(response); addCORSHeaders(response);
request->send(response); request->send(response);
return; 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 // CORS request, no options => let the request pass and add CORS headers after
next(); next();
AsyncWebServerResponse* response = request->getResponse(); AsyncWebServerResponse *response = request->getResponse();
if (response) { if (response) {
addCORSHeaders(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(); uint32_t now = millis();
while (!_requestTimes.empty() && _requestTimes.front() <= now - _windowSizeMillis) while (!_requestTimes.empty() && _requestTimes.front() <= now - _windowSizeMillis) {
_requestTimes.pop_front(); _requestTimes.pop_front();
}
_requestTimes.push_back(now); _requestTimes.push_back(now);
@ -244,12 +275,12 @@ bool AsyncRateLimitMiddleware::isRequestAllowed(uint32_t& retryAfterSeconds) {
return true; return true;
} }
void AsyncRateLimitMiddleware::run(AsyncWebServerRequest* request, ArMiddlewareNext next) { void AsyncRateLimitMiddleware::run(AsyncWebServerRequest *request, ArMiddlewareNext next) {
uint32_t retryAfterSeconds; uint32_t retryAfterSeconds;
if (isRequestAllowed(retryAfterSeconds)) { if (isRequestAllowed(retryAfterSeconds)) {
next(); next();
} else { } else {
AsyncWebServerResponse* response = request->beginResponse(429); AsyncWebServerResponse *response = request->beginResponse(429);
response->addHeader(asyncsrv::T_retry_after, retryAfterSeconds); response->addHeader(asyncsrv::T_retry_after, retryAfterSeconds);
request->send(response); request->send(response);
} }

View file

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

View file

@ -1,37 +1,22 @@
/* // SPDX-License-Identifier: LGPL-3.0-or-later
Asynchronous WebServer library for Espressif MCUs // 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 WEB_AUTHENTICATION_H_ #ifndef WEB_AUTHENTICATION_H_
#define WEB_AUTHENTICATION_H_ #define WEB_AUTHENTICATION_H_
#include "Arduino.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 // 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(); String genRandomMD5();

View file

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

View file

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

View file

@ -1,30 +1,13 @@
/* // SPDX-License-Identifier: LGPL-3.0-or-later
Asynchronous WebServer library for Espressif MCUs // 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_ #ifndef ASYNCWEBSERVERRESPONSEIMPL_H_
#define ASYNCWEBSERVERRESPONSEIMPL_H_ #define ASYNCWEBSERVERRESPONSEIMPL_H_
#ifdef Arduino_h #ifdef Arduino_h
// arduino is not compatible with std::vector // arduino is not compatible with std::vector
#undef min #undef min
#undef max #undef max
#endif #endif
#include "literals.h" #include "literals.h"
#include <StreamString.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. // It is possible to restore these defines, but one can use _min and _max instead. Or std::min, std::max.
class AsyncBasicResponse : public AsyncWebServerResponse { class AsyncBasicResponse : public AsyncWebServerResponse {
private: private:
String _content; String _content;
public: public:
explicit AsyncBasicResponse(int code, const char* contentType = asyncsrv::empty, const char* content = asyncsrv::empty); 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()) {} AsyncBasicResponse(int code, const String &contentType, const String &content = emptyString)
void _respond(AsyncWebServerRequest* request) override final; : AsyncBasicResponse(code, contentType.c_str(), content.c_str()) {}
size_t _ack(AsyncWebServerRequest* request, size_t len, uint32_t time) override final; void _respond(AsyncWebServerRequest *request) override final;
bool _sourceValid() const override final { return true; } size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time) override final;
bool _sourceValid() const override final {
return true;
}
}; };
class AsyncAbstractResponse : public AsyncWebServerResponse { class AsyncAbstractResponse : public AsyncWebServerResponse {
private: private:
#if ASYNCWEBSERVER_USE_CHUNK_INFLIGHT #if ASYNCWEBSERVER_USE_CHUNK_INFLIGHT
// amount of responce data in-flight, i.e. sent, but not acked yet // amount of response data in-flight, i.e. sent, but not acked yet
size_t _in_flight{0}; size_t _in_flight{0};
// in-flight queue credits // in-flight queue credits
size_t _in_flight_credit{2}; size_t _in_flight_credit{2};
#endif #endif
String _head; String _head;
// Data is inserted into cache at begin(). // Data is inserted into cache at begin().
// This is inefficient with vector, but if we use some other container, // 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, // 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. // so by gaining performance in one place, we'll lose it in another.
std::vector<uint8_t> _cache; std::vector<uint8_t> _cache;
size_t _readDataFromCacheOrContent(uint8_t* data, const size_t len); size_t _readDataFromCacheOrContent(uint8_t *data, const size_t len);
size_t _fillBufferAndProcessTemplates(uint8_t* buf, size_t maxLen); size_t _fillBufferAndProcessTemplates(uint8_t *buf, size_t maxLen);
protected: protected:
AwsTemplateProcessor _callback; AwsTemplateProcessor _callback;
public: public:
AsyncAbstractResponse(AwsTemplateProcessor callback = nullptr); AsyncAbstractResponse(AwsTemplateProcessor callback = nullptr);
virtual ~AsyncAbstractResponse() {} virtual ~AsyncAbstractResponse() {}
void _respond(AsyncWebServerRequest* request) override final; void _respond(AsyncWebServerRequest *request) override final;
size_t _ack(AsyncWebServerRequest* request, size_t len, uint32_t time) override final; size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time) override final;
virtual bool _sourceValid() const { return false; } virtual bool _sourceValid() const {
virtual size_t _fillBuffer(uint8_t* buf __attribute__((unused)), size_t maxLen __attribute__((unused))) { return 0; } return false;
}
virtual size_t _fillBuffer(uint8_t *buf __attribute__((unused)), size_t maxLen __attribute__((unused))) {
return 0;
}
}; };
#ifndef TEMPLATE_PLACEHOLDER #ifndef TEMPLATE_PLACEHOLDER
#define TEMPLATE_PLACEHOLDER '%' #define TEMPLATE_PLACEHOLDER '%'
#endif #endif
#define TEMPLATE_PARAM_NAME_LENGTH 32 #define TEMPLATE_PARAM_NAME_LENGTH 32
class AsyncFileResponse : public AsyncAbstractResponse { class AsyncFileResponse : public AsyncAbstractResponse {
using File = fs::File; using File = fs::File;
using FS = fs::FS; using FS = fs::FS;
private: private:
File _content; File _content;
String _path; String _path;
void _setContentTypeFromPath(const String& path); void _setContentTypeFromPath(const String &path);
public: 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 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(FS &fs, const String &path, const String &contentType, bool download = false, AwsTemplateProcessor callback = nullptr)
AsyncFileResponse(File content, const String& path, const char* contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr); : AsyncFileResponse(fs, path, contentType.c_str(), download, callback) {}
AsyncFileResponse(File content, const String& path, const String& contentType, bool download = false, AwsTemplateProcessor callack = nullptr) : AsyncFileResponse(content, path, contentType.c_str(), download, callack) {} AsyncFileResponse(
~AsyncFileResponse() { _content.close(); } File content, const String &path, const char *contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr
bool _sourceValid() const override final { return !!(_content); } );
size_t _fillBuffer(uint8_t* buf, size_t maxLen) override final; 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 { class AsyncStreamResponse : public AsyncAbstractResponse {
private: private:
Stream* _content; Stream *_content;
public: public:
AsyncStreamResponse(Stream& stream, const char* contentType, size_t len, AwsTemplateProcessor callback = nullptr); 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) {} AsyncStreamResponse(Stream &stream, const String &contentType, size_t len, AwsTemplateProcessor callback = nullptr)
bool _sourceValid() const override final { return !!(_content); } : AsyncStreamResponse(stream, contentType.c_str(), len, callback) {}
size_t _fillBuffer(uint8_t* buf, size_t maxLen) override final; bool _sourceValid() const override final {
return !!(_content);
}
size_t _fillBuffer(uint8_t *buf, size_t maxLen) override final;
}; };
class AsyncCallbackResponse : public AsyncAbstractResponse { class AsyncCallbackResponse : public AsyncAbstractResponse {
private: private:
AwsResponseFiller _content; AwsResponseFiller _content;
size_t _filledLength; size_t _filledLength;
public: public:
AsyncCallbackResponse(const char* contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr); 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) {} AsyncCallbackResponse(const String &contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr)
bool _sourceValid() const override final { return !!(_content); } : AsyncCallbackResponse(contentType.c_str(), len, callback, templateCallback) {}
size_t _fillBuffer(uint8_t* buf, size_t maxLen) override final; bool _sourceValid() const override final {
return !!(_content);
}
size_t _fillBuffer(uint8_t *buf, size_t maxLen) override final;
}; };
class AsyncChunkedResponse : public AsyncAbstractResponse { class AsyncChunkedResponse : public AsyncAbstractResponse {
private: private:
AwsResponseFiller _content; AwsResponseFiller _content;
size_t _filledLength; size_t _filledLength;
public: public:
AsyncChunkedResponse(const char* contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr); AsyncChunkedResponse(const char *contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr);
AsyncChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) : AsyncChunkedResponse(contentType.c_str(), callback, templateCallback) {} AsyncChunkedResponse(const String &contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr)
bool _sourceValid() const override final { return !!(_content); } : AsyncChunkedResponse(contentType.c_str(), callback, templateCallback) {}
size_t _fillBuffer(uint8_t* buf, size_t maxLen) override final; bool _sourceValid() const override final {
return !!(_content);
}
size_t _fillBuffer(uint8_t *buf, size_t maxLen) override final;
}; };
class AsyncProgmemResponse : public AsyncAbstractResponse { class AsyncProgmemResponse : public AsyncAbstractResponse {
private: private:
const uint8_t* _content; const uint8_t *_content;
size_t _readLength; size_t _readLength;
public: public:
AsyncProgmemResponse(int code, const char* contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr); 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) {} AsyncProgmemResponse(int code, const String &contentType, const uint8_t *content, size_t len, AwsTemplateProcessor callback = nullptr)
bool _sourceValid() const override final { return true; } : AsyncProgmemResponse(code, contentType.c_str(), content, len, callback) {}
size_t _fillBuffer(uint8_t* buf, size_t maxLen) override final; bool _sourceValid() const override final {
return true;
}
size_t _fillBuffer(uint8_t *buf, size_t maxLen) override final;
}; };
class AsyncResponseStream : public AsyncAbstractResponse, public Print { class AsyncResponseStream : public AsyncAbstractResponse, public Print {
private: private:
StreamString _content; StreamString _content;
public: public:
AsyncResponseStream(const char* contentType, size_t bufferSize); AsyncResponseStream(const char *contentType, size_t bufferSize);
AsyncResponseStream(const String& contentType, size_t bufferSize) : AsyncResponseStream(contentType.c_str(), bufferSize) {} AsyncResponseStream(const String &contentType, size_t bufferSize) : AsyncResponseStream(contentType.c_str(), bufferSize) {}
bool _sourceValid() const override final { return (_state < RESPONSE_END); } bool _sourceValid() const override final {
size_t _fillBuffer(uint8_t* buf, size_t maxLen) override final; return (_state < RESPONSE_END);
size_t write(const uint8_t* data, size_t len); }
size_t write(uint8_t data); size_t _fillBuffer(uint8_t *buf, size_t maxLen) override final;
using Print::write; size_t write(const uint8_t *data, size_t len);
size_t write(uint8_t data);
using Print::write;
}; };
#endif /* ASYNCWEBSERVERRESPONSEIMPL_H_ */ #endif /* ASYNCWEBSERVERRESPONSEIMPL_H_ */

View file

@ -1,34 +1,19 @@
/* // SPDX-License-Identifier: LGPL-3.0-or-later
Asynchronous WebServer library for Espressif MCUs // 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 "ESPAsyncWebServer.h"
#include "WebResponseImpl.h" #include "WebResponseImpl.h"
using namespace asyncsrv; using namespace asyncsrv;
// Since ESP8266 does not link memchr by default, here's its implementation. // Since ESP8266 does not link memchr by default, here's its implementation.
void* memchr(void* ptr, int ch, size_t count) { void *memchr(void *ptr, int ch, size_t count) {
unsigned char* p = static_cast<unsigned char*>(ptr); unsigned char *p = static_cast<unsigned char *>(ptr);
while (count--) while (count--) {
if (*p++ == static_cast<unsigned char>(ch)) if (*p++ == static_cast<unsigned char>(ch)) {
return --p; return --p;
}
}
return nullptr; 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) { switch (code) {
case 100: case 100: return T_HTTP_CODE_100;
return T_HTTP_CODE_100; case 101: return T_HTTP_CODE_101;
case 101: case 200: return T_HTTP_CODE_200;
return T_HTTP_CODE_101; case 201: return T_HTTP_CODE_201;
case 200: case 202: return T_HTTP_CODE_202;
return T_HTTP_CODE_200; case 203: return T_HTTP_CODE_203;
case 201: case 204: return T_HTTP_CODE_204;
return T_HTTP_CODE_201; case 205: return T_HTTP_CODE_205;
case 202: case 206: return T_HTTP_CODE_206;
return T_HTTP_CODE_202; case 300: return T_HTTP_CODE_300;
case 203: case 301: return T_HTTP_CODE_301;
return T_HTTP_CODE_203; case 302: return T_HTTP_CODE_302;
case 204: case 303: return T_HTTP_CODE_303;
return T_HTTP_CODE_204; case 304: return T_HTTP_CODE_304;
case 205: case 305: return T_HTTP_CODE_305;
return T_HTTP_CODE_205; case 307: return T_HTTP_CODE_307;
case 206: case 400: return T_HTTP_CODE_400;
return T_HTTP_CODE_206; case 401: return T_HTTP_CODE_401;
case 300: case 402: return T_HTTP_CODE_402;
return T_HTTP_CODE_300; case 403: return T_HTTP_CODE_403;
case 301: case 404: return T_HTTP_CODE_404;
return T_HTTP_CODE_301; case 405: return T_HTTP_CODE_405;
case 302: case 406: return T_HTTP_CODE_406;
return T_HTTP_CODE_302; case 407: return T_HTTP_CODE_407;
case 303: case 408: return T_HTTP_CODE_408;
return T_HTTP_CODE_303; case 409: return T_HTTP_CODE_409;
case 304: case 410: return T_HTTP_CODE_410;
return T_HTTP_CODE_304; case 411: return T_HTTP_CODE_411;
case 305: case 412: return T_HTTP_CODE_412;
return T_HTTP_CODE_305; case 413: return T_HTTP_CODE_413;
case 307: case 414: return T_HTTP_CODE_414;
return T_HTTP_CODE_307; case 415: return T_HTTP_CODE_415;
case 400: case 416: return T_HTTP_CODE_416;
return T_HTTP_CODE_400; case 417: return T_HTTP_CODE_417;
case 401: case 429: return T_HTTP_CODE_429;
return T_HTTP_CODE_401; case 500: return T_HTTP_CODE_500;
case 402: case 501: return T_HTTP_CODE_501;
return T_HTTP_CODE_402; case 502: return T_HTTP_CODE_502;
case 403: case 503: return T_HTTP_CODE_503;
return T_HTTP_CODE_403; case 504: return T_HTTP_CODE_504;
case 404: case 505: return T_HTTP_CODE_505;
return T_HTTP_CODE_404; default: return T_HTTP_CODE_ANY;
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() AsyncWebServerResponse::AsyncWebServerResponse()
: _code(0), _contentType(), _contentLength(0), _sendContentLength(true), _chunked(false), _headLength(0), _sentLength(0), _ackedLength(0), _writtenLength(0), _state(RESPONSE_SETUP) { : _code(0), _contentType(), _contentLength(0), _sendContentLength(true), _chunked(false), _headLength(0), _sentLength(0), _ackedLength(0), _writtenLength(0),
for (const auto& header : DefaultHeaders::Instance()) { _state(RESPONSE_SETUP) {
for (const auto &header : DefaultHeaders::Instance()) {
_headers.emplace_back(header); _headers.emplace_back(header);
} }
} }
void AsyncWebServerResponse::setCode(int code) { void AsyncWebServerResponse::setCode(int code) {
if (_state == RESPONSE_SETUP) if (_state == RESPONSE_SETUP) {
_code = code; _code = code;
}
} }
void AsyncWebServerResponse::setContentLength(size_t len) { 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; _contentLength = len;
}
} }
void AsyncWebServerResponse::setContentType(const char* type) { void AsyncWebServerResponse::setContentType(const char *type) {
if (_state == RESPONSE_SETUP && addHeader(T_Content_Type, type, true)) if (_state == RESPONSE_SETUP && addHeader(T_Content_Type, type, true)) {
_contentType = type; _contentType = type;
}
} }
bool AsyncWebServerResponse::removeHeader(const char* name) { bool AsyncWebServerResponse::removeHeader(const char *name) {
for (auto i = _headers.begin(); i != _headers.end(); ++i) { bool h_erased = false;
for (auto i = _headers.begin(); i != _headers.end();) {
if (i->name().equalsIgnoreCase(name)) { 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); _headers.erase(i);
return true; return true;
} }
@ -158,12 +118,23 @@ bool AsyncWebServerResponse::removeHeader(const char* name) {
return false; return false;
} }
const AsyncWebHeader* AsyncWebServerResponse::getHeader(const char* name) const { 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); }); 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); 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) { for (auto i = _headers.begin(); i != _headers.end(); ++i) {
if (i->name().equalsIgnoreCase(name)) { if (i->name().equalsIgnoreCase(name)) {
// header already set // header already set
@ -171,9 +142,11 @@ bool AsyncWebServerResponse::addHeader(const char* name, const char* value, bool
// remove, break and add the new one // remove, break and add the new one
_headers.erase(i); _headers.erase(i);
break; break;
} else { } else if (headerMustBePresentOnce(i->name())) { // we can have only one header with that name
// do not update // do not update
return false; 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; return true;
} }
void AsyncWebServerResponse::_assembleHead(String& buffer, uint8_t version) { void AsyncWebServerResponse::_assembleHead(String &buffer, uint8_t version) {
if (version) { if (version) {
addHeader(T_Accept_Ranges, T_none, false); addHeader(T_Accept_Ranges, T_none, false);
if (_chunked) if (_chunked) {
addHeader(T_Transfer_Encoding, T_chunked, false); addHeader(T_Transfer_Encoding, T_chunked, false);
}
} }
if (_sendContentLength) if (_sendContentLength) {
addHeader(T_Content_Length, String(_contentLength), false); addHeader(T_Content_Length, String(_contentLength), false);
}
if (_contentType.length()) if (_contentType.length()) {
addHeader(T_Content_Type, _contentType.c_str(), false); addHeader(T_Content_Type, _contentType.c_str(), false);
}
// precompute buffer size to avoid reallocations by String class // precompute buffer size to avoid reallocations by String class
size_t len = 0; size_t len = 0;
len += 50; // HTTP/1.1 200 <reason>\r\n len += 50; // HTTP/1.1 200 <reason>\r\n
for (const auto& header : _headers) for (const auto &header : _headers) {
len += header.name().length() + header.value().length() + 4; len += header.name().length() + header.value().length() + 4;
}
// prepare buffer // prepare buffer
buffer.reserve(len); buffer.reserve(len);
@ -218,7 +195,7 @@ void AsyncWebServerResponse::_assembleHead(String& buffer, uint8_t version) {
buffer.concat(T_rn); buffer.concat(T_rn);
// Add headers // Add headers
for (const auto& header : _headers) { for (const auto &header : _headers) {
buffer.concat(header.name()); buffer.concat(header.name());
#ifdef ESP8266 #ifdef ESP8266
buffer.concat(PSTR(": ")); buffer.concat(PSTR(": "));
@ -233,15 +210,23 @@ void AsyncWebServerResponse::_assembleHead(String& buffer, uint8_t version) {
_headLength = buffer.length(); _headLength = buffer.length();
} }
bool AsyncWebServerResponse::_started() const { return _state > RESPONSE_SETUP; } bool AsyncWebServerResponse::_started() const {
bool AsyncWebServerResponse::_finished() const { return _state > RESPONSE_WAIT_ACK; } return _state > RESPONSE_SETUP;
bool AsyncWebServerResponse::_failed() const { return _state == RESPONSE_FAILED; } }
bool AsyncWebServerResponse::_sourceValid() const { return false; } bool AsyncWebServerResponse::_finished() const {
void AsyncWebServerResponse::_respond(AsyncWebServerRequest* request) { 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; _state = RESPONSE_END;
request->client()->close(); 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)request;
(void)len; (void)len;
(void)time; (void)time;
@ -251,19 +236,20 @@ size_t AsyncWebServerResponse::_ack(AsyncWebServerRequest* request, size_t len,
/* /*
* String/Code Response * 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; _code = code;
_content = content; _content = content;
_contentType = contentType; _contentType = contentType;
if (_content.length()) { if (_content.length()) {
_contentLength = _content.length(); _contentLength = _content.length();
if (!_contentType.length()) if (!_contentType.length()) {
_contentType = T_text_plain; _contentType = T_text_plain;
}
} }
addHeader(T_Connection, T_close, false); addHeader(T_Connection, T_close, false);
} }
void AsyncBasicResponse::_respond(AsyncWebServerRequest* request) { void AsyncBasicResponse::_respond(AsyncWebServerRequest *request) {
_state = RESPONSE_HEADERS; _state = RESPONSE_HEADERS;
String out; String out;
_assembleHead(out, request->version()); _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; (void)time;
_ackedLength += len; _ackedLength += len;
if (_state == RESPONSE_CONTENT) { 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); addHeader(T_Connection, T_close, false);
_assembleHead(_head, request->version()); _assembleHead(_head, request->version());
_state = RESPONSE_HEADERS; _state = RESPONSE_HEADERS;
_ack(request, 0, 0); _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; (void)time;
if (!_sourceValid()) { if (!_sourceValid()) {
_state = RESPONSE_FAILED; _state = RESPONSE_FAILED;
@ -355,14 +341,15 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest* request, size_t len, u
#if ASYNCWEBSERVER_USE_CHUNK_INFLIGHT #if ASYNCWEBSERVER_USE_CHUNK_INFLIGHT
// return a credit for each chunk of acked data (polls does not give any credits) // return a credit for each chunk of acked data (polls does not give any credits)
if (len) if (len) {
++_in_flight_credit; ++_in_flight_credit;
}
// for chunked responses ignore acks if there are no _in_flight_credits left // for chunked responses ignore acks if there are no _in_flight_credits left
if (_chunked && !_in_flight_credit) { if (_chunked && !_in_flight_credit) {
#ifdef ESP32 #ifdef ESP32
log_d("(chunk) out of in-flight credits"); log_d("(chunk) out of in-flight credits");
#endif #endif
return 0; 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()); _writtenLength += request->client()->write(out.c_str(), out.length());
#if ASYNCWEBSERVER_USE_CHUNK_INFLIGHT #if ASYNCWEBSERVER_USE_CHUNK_INFLIGHT
_in_flight += out.length(); _in_flight += out.length();
--_in_flight_credit; // take a credit --_in_flight_credit; // take a credit
#endif #endif
return out.length(); return out.length();
} }
@ -399,8 +386,9 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest* request, size_t len, u
if (_in_flight > space) { if (_in_flight > space) {
// log_d("defer user call %u/%u", _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 // take the credit back since we are ignoring this ack and rely on other inflight data
if (len) if (len) {
--_in_flight_credit; --_in_flight_credit;
}
return 0; return 0;
} }
#endif #endif
@ -418,9 +406,12 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest* request, size_t len, u
outLen = ((_contentLength - _sentLength) > space) ? space : (_contentLength - _sentLength); 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) { if (!buf) {
// os_printf("_ack malloc %d failed\n", outLen+headLen); #ifdef ESP32
log_e("Failed to allocate");
#endif
request->abort();
return 0; return 0;
} }
@ -438,7 +429,7 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest* request, size_t len, u
free(buf); free(buf);
return 0; return 0;
} }
outLen = sprintf((char*)buf + headLen, "%04x", readLen) + headLen; outLen = sprintf((char *)buf + headLen, "%04x", readLen) + headLen;
buf[outLen++] = '\r'; buf[outLen++] = '\r';
buf[outLen++] = '\n'; buf[outLen++] = '\n';
outLen += readLen; outLen += readLen;
@ -458,10 +449,10 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest* request, size_t len, u
} }
if (outLen) { if (outLen) {
_writtenLength += request->client()->write((const char*)buf, outLen); _writtenLength += request->client()->write((const char *)buf, outLen);
#if ASYNCWEBSERVER_USE_CHUNK_INFLIGHT #if ASYNCWEBSERVER_USE_CHUNK_INFLIGHT
_in_flight += outLen; _in_flight += outLen;
--_in_flight_credit; // take a credit --_in_flight_credit; // take a credit
#endif #endif
} }
@ -481,14 +472,15 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest* request, size_t len, u
} else if (_state == RESPONSE_WAIT_ACK) { } else if (_state == RESPONSE_WAIT_ACK) {
if (!_sendContentLength || _ackedLength >= _writtenLength) { if (!_sendContentLength || _ackedLength >= _writtenLength) {
_state = RESPONSE_END; _state = RESPONSE_END;
if (!_chunked && !_sendContentLength) if (!_chunked && !_sendContentLength) {
request->client()->close(true); request->client()->close(true);
}
} }
} }
return 0; 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 // If we have something in cache, copy it to buffer
const size_t readFromCache = std::min(len, _cache.size()); const size_t readFromCache = std::min(len, _cache.size());
if (readFromCache) { if (readFromCache) {
@ -501,17 +493,20 @@ size_t AsyncAbstractResponse::_readDataFromCacheOrContent(uint8_t* data, const s
return readFromCache + readFromContent; return readFromCache + readFromContent;
} }
size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size_t len) { size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t *data, size_t len) {
if (!_callback) if (!_callback) {
return _fillBuffer(data, len); return _fillBuffer(data, len);
}
const size_t originalLen = len; const size_t originalLen = len;
len = _readDataFromCacheOrContent(data, len); len = _readDataFromCacheOrContent(data, len);
// Now we've read 'len' bytes, either from cache or from file // Now we've read 'len' bytes, either from cache or from file
// Search for template placeholders // Search for template placeholders
uint8_t* pTemplateStart = data; 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] while ((pTemplateStart < &data[len]) && (pTemplateStart = (uint8_t *)memchr(pTemplateStart, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart + 1))
uint8_t* pTemplateEnd = (pTemplateStart < &data[len - 1]) ? (uint8_t*)memchr(pTemplateStart + 1, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart) : nullptr; ) { // 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 // temporary buffer to hold parameter name
uint8_t buf[TEMPLATE_PARAM_NAME_LENGTH + 1]; uint8_t buf[TEMPLATE_PARAM_NAME_LENGTH + 1];
String paramName; String paramName;
@ -522,35 +517,39 @@ size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size
if (paramNameLength) { if (paramNameLength) {
memcpy(buf, pTemplateStart + 1, paramNameLength); memcpy(buf, pTemplateStart + 1, paramNameLength);
buf[paramNameLength] = 0; buf[paramNameLength] = 0;
paramName = String(reinterpret_cast<char*>(buf)); paramName = String(reinterpret_cast<char *>(buf));
} else { // double percent sign encountered, this is single percent sign escaped. } else { // double percent sign encountered, this is single percent sign escaped.
// remove the 2nd percent sign // remove the 2nd percent sign
memmove(pTemplateEnd, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1); memmove(pTemplateEnd, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1);
len += _readDataFromCacheOrContent(&data[len - 1], 1) - 1; len += _readDataFromCacheOrContent(&data[len - 1], 1) - 1;
++pTemplateStart; ++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); 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) { 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) { if (pTemplateEnd) {
// prepare argument to callback // prepare argument to callback
*pTemplateEnd = 0; *pTemplateEnd = 0;
paramName = String(reinterpret_cast<char*>(buf)); paramName = String(reinterpret_cast<char *>(buf));
// Copy remaining read-ahead data into cache // Copy remaining read-ahead data into cache
_cache.insert(_cache.begin(), pTemplateEnd + 1, buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent); _cache.insert(_cache.begin(), pTemplateEnd + 1, buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent);
pTemplateEnd = &data[len - 1]; 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 // but first, store read file data in cache
_cache.insert(_cache.begin(), buf + (&data[len - 1] - pTemplateStart), buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent); _cache.insert(_cache.begin(), buf + (&data[len - 1] - pTemplateStart), buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent);
++pTemplateStart; ++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; ++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; ++pTemplateStart;
}
if (paramName.length()) { if (paramName.length()) {
// call callback and replace with result. // call callback and replace with result.
// Everything in range [pTemplateStart, pTemplateEnd] can be safely replaced with parameter value. // 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. // 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). // It should be located at pTemplateStart + numBytesCopied (to begin right after inserted parameter value).
const String paramValue(_callback(paramName)); const String paramValue(_callback(paramName));
const char* pvstr = paramValue.c_str(); const char *pvstr = paramValue.c_str();
const unsigned int pvlen = paramValue.length(); const unsigned int pvlen = paramValue.length();
const size_t numBytesCopied = std::min(pvlen, static_cast<unsigned int>(&data[originalLen - 1] - pTemplateStart + 1)); const size_t numBytesCopied = std::min(pvlen, static_cast<unsigned int>(&data[originalLen - 1] - pTemplateStart + 1));
// make room for param value // 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]); _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 // 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); memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[originalLen] - pTemplateStart - numBytesCopied);
len = originalLen; // fix issue with truncated data, not sure if it has any side effects len = originalLen; // fix issue with truncated data, not sure if it has any side effects
} else if (pTemplateEnd + 1 != pTemplateStart + numBytesCopied) } 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. // 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 // Move the entire data after the placeholder
memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1); memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1);
}
// 3. replace placeholder with actual value // 3. replace placeholder with actual value
memcpy(pTemplateStart, pvstr, numBytesCopied); 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 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) { if (numBytesCopied < pvlen) {
_cache.insert(_cache.begin(), pvstr + numBytesCopied, pvstr + 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 // there is some free room, fill it from cache
const size_t roomFreed = pTemplateEnd + 1 - pTemplateStart - numBytesCopied; const size_t roomFreed = pTemplateEnd + 1 - pTemplateStart - numBytesCopied;
const size_t totalFreeRoom = originalLen - len + roomFreed; const size_t totalFreeRoom = originalLen - len + roomFreed;
len += _readDataFromCacheOrContent(&data[len - roomFreed], totalFreeRoom) - 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; const size_t roomTaken = pTemplateStart + numBytesCopied - pTemplateEnd - 1;
len = std::min(len + roomTaken, originalLen); len = std::min(len + roomTaken, originalLen);
} }
} }
} // while(pTemplateStart) } // while(pTemplateStart)
return len; return len;
} }
@ -595,64 +595,66 @@ size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size
* File Response * File Response
* */ * */
void AsyncFileResponse::_setContentTypeFromPath(const String& path) { void AsyncFileResponse::_setContentTypeFromPath(const String &path) {
#if HAVE_EXTERN_GET_Content_Type_FUNCTION #if HAVE_EXTERN_GET_Content_Type_FUNCTION
#ifndef ESP8266 #ifndef ESP8266
extern const char* getContentType(const String& path); extern const char *getContentType(const String &path);
#else #else
extern const __FlashStringHelper* getContentType(const String& path); extern const __FlashStringHelper *getContentType(const String &path);
#endif #endif
_contentType = getContentType(path); _contentType = getContentType(path);
#else #else
if (path.endsWith(T__html)) if (path.endsWith(T__html)) {
_contentType = T_text_html; _contentType = T_text_html;
else if (path.endsWith(T__htm)) } else if (path.endsWith(T__htm)) {
_contentType = T_text_html; _contentType = T_text_html;
else if (path.endsWith(T__css)) } else if (path.endsWith(T__css)) {
_contentType = T_text_css; _contentType = T_text_css;
else if (path.endsWith(T__json)) } else if (path.endsWith(T__json)) {
_contentType = T_application_json; _contentType = T_application_json;
else if (path.endsWith(T__js)) } else if (path.endsWith(T__js)) {
_contentType = T_application_javascript; _contentType = T_application_javascript;
else if (path.endsWith(T__png)) } else if (path.endsWith(T__png)) {
_contentType = T_image_png; _contentType = T_image_png;
else if (path.endsWith(T__gif)) } else if (path.endsWith(T__gif)) {
_contentType = T_image_gif; _contentType = T_image_gif;
else if (path.endsWith(T__jpg)) } else if (path.endsWith(T__jpg)) {
_contentType = T_image_jpeg; _contentType = T_image_jpeg;
else if (path.endsWith(T__ico)) } else if (path.endsWith(T__ico)) {
_contentType = T_image_x_icon; _contentType = T_image_x_icon;
else if (path.endsWith(T__svg)) } else if (path.endsWith(T__svg)) {
_contentType = T_image_svg_xml; _contentType = T_image_svg_xml;
else if (path.endsWith(T__eot)) } else if (path.endsWith(T__eot)) {
_contentType = T_font_eot; _contentType = T_font_eot;
else if (path.endsWith(T__woff)) } else if (path.endsWith(T__woff)) {
_contentType = T_font_woff; _contentType = T_font_woff;
else if (path.endsWith(T__woff2)) } else if (path.endsWith(T__woff2)) {
_contentType = T_font_woff2; _contentType = T_font_woff2;
else if (path.endsWith(T__ttf)) } else if (path.endsWith(T__ttf)) {
_contentType = T_font_ttf; _contentType = T_font_ttf;
else if (path.endsWith(T__xml)) } else if (path.endsWith(T__xml)) {
_contentType = T_text_xml; _contentType = T_text_xml;
else if (path.endsWith(T__pdf)) } else if (path.endsWith(T__pdf)) {
_contentType = T_application_pdf; _contentType = T_application_pdf;
else if (path.endsWith(T__zip)) } else if (path.endsWith(T__zip)) {
_contentType = T_application_zip; _contentType = T_application_zip;
else if (path.endsWith(T__gz)) } else if (path.endsWith(T__gz)) {
_contentType = T_application_x_gzip; _contentType = T_application_x_gzip;
else } else {
_contentType = T_text_plain; _contentType = T_text_plain;
}
#endif #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; _code = 200;
_path = path; _path = path;
if (!download && !fs.exists(_path) && fs.exists(_path + T__gz)) { if (!download && !fs.exists(_path) && fs.exists(_path + T__gz)) {
_path = _path + T__gz; _path = _path + T__gz;
addHeader(T_Content_Encoding, T_gzip, false); addHeader(T_Content_Encoding, T_gzip, false);
_callback = nullptr; // Unable to process zipped templates _callback = nullptr; // Unable to process zipped templates
_sendContentLength = true; _sendContentLength = true;
_chunked = false; _chunked = false;
} }
@ -660,14 +662,15 @@ AsyncFileResponse::AsyncFileResponse(FS& fs, const String& path, const char* con
_content = fs.open(_path, fs::FileOpenMode::read); _content = fs.open(_path, fs::FileOpenMode::read);
_contentLength = _content.size(); _contentLength = _content.size();
if (strlen(contentType) == 0) if (strlen(contentType) == 0) {
_setContentTypeFromPath(path); _setContentTypeFromPath(path);
else } else {
_contentType = contentType; _contentType = contentType;
}
int filenameStart = path.lastIndexOf('/') + 1; int filenameStart = path.lastIndexOf('/') + 1;
char buf[26 + path.length() - filenameStart]; char buf[26 + path.length() - filenameStart];
char* filename = (char*)path.c_str() + filenameStart; char *filename = (char *)path.c_str() + filenameStart;
if (download) { if (download) {
// set filename and force 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); 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; _code = 200;
_path = path; _path = path;
if (!download && String(content.name()).endsWith(T__gz) && !path.endsWith(T__gz)) { if (!download && String(content.name()).endsWith(T__gz) && !path.endsWith(T__gz)) {
addHeader(T_Content_Encoding, T_gzip, false); addHeader(T_Content_Encoding, T_gzip, false);
_callback = nullptr; // Unable to process gzipped templates _callback = nullptr; // Unable to process gzipped templates
_sendContentLength = true; _sendContentLength = true;
_chunked = false; _chunked = false;
} }
@ -693,14 +697,15 @@ AsyncFileResponse::AsyncFileResponse(File content, const String& path, const cha
_content = content; _content = content;
_contentLength = _content.size(); _contentLength = _content.size();
if (strlen(contentType) == 0) if (strlen(contentType) == 0) {
_setContentTypeFromPath(path); _setContentTypeFromPath(path);
else } else {
_contentType = contentType; _contentType = contentType;
}
int filenameStart = path.lastIndexOf('/') + 1; int filenameStart = path.lastIndexOf('/') + 1;
char buf[26 + path.length() - filenameStart]; char buf[26 + path.length() - filenameStart];
char* filename = (char*)path.c_str() + filenameStart; char *filename = (char *)path.c_str() + filenameStart;
if (download) { if (download) {
snprintf_P(buf, sizeof(buf), PSTR("attachment; filename=\"%s\""), filename); 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); 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); return _content.read(data, len);
} }
@ -718,19 +723,20 @@ size_t AsyncFileResponse::_fillBuffer(uint8_t* data, size_t len) {
* Stream Response * 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; _code = 200;
_content = &stream; _content = &stream;
_contentLength = len; _contentLength = len;
_contentType = contentType; _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 available = _content->available();
size_t outLen = (available > len) ? len : available; size_t outLen = (available > len) ? len : available;
size_t i; size_t i;
for (i = 0; i < outLen; i++) for (i = 0; i < outLen; i++) {
data[i] = _content->read(); data[i] = _content->read();
}
return outLen; return outLen;
} }
@ -738,17 +744,19 @@ size_t AsyncStreamResponse::_fillBuffer(uint8_t* data, size_t len) {
* Callback Response * 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; _code = 200;
_content = callback; _content = callback;
_contentLength = len; _contentLength = len;
if (!len) if (!len) {
_sendContentLength = false; _sendContentLength = false;
}
_contentType = contentType; _contentType = contentType;
_filledLength = 0; _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); size_t ret = _content(data, len, _filledLength);
if (ret != RESPONSE_TRY_AGAIN) { if (ret != RESPONSE_TRY_AGAIN) {
_filledLength += ret; _filledLength += ret;
@ -760,7 +768,8 @@ size_t AsyncCallbackResponse::_fillBuffer(uint8_t* data, size_t len) {
* Chunked Response * 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; _code = 200;
_content = callback; _content = callback;
_contentLength = 0; _contentLength = 0;
@ -770,7 +779,7 @@ AsyncChunkedResponse::AsyncChunkedResponse(const char* contentType, AwsResponseF
_filledLength = 0; _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); size_t ret = _content(data, len, _filledLength);
if (ret != RESPONSE_TRY_AGAIN) { if (ret != RESPONSE_TRY_AGAIN) {
_filledLength += ret; _filledLength += ret;
@ -782,7 +791,8 @@ size_t AsyncChunkedResponse::_fillBuffer(uint8_t* data, size_t len) {
* Progmem Response * 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; _code = code;
_content = content; _content = content;
_contentType = contentType; _contentType = contentType;
@ -790,7 +800,7 @@ AsyncProgmemResponse::AsyncProgmemResponse(int code, const char* contentType, co
_readLength = 0; _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; size_t left = _contentLength - _readLength;
if (left > len) { if (left > len) {
memcpy_P(data, _content + _readLength, 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) * 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; _code = 200;
_contentLength = 0; _contentLength = 0;
_contentType = contentType; _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) { size_t AsyncResponseStream::_fillBuffer(uint8_t *buf, size_t maxLen) {
return _content.readBytes((char*)buf, maxLen); return _content.readBytes((char *)buf, maxLen);
} }
size_t AsyncResponseStream::write(const uint8_t* data, size_t len) { size_t AsyncResponseStream::write(const uint8_t *data, size_t len) {
if (_started()) if (_started()) {
return 0; return 0;
}
size_t written = _content.write(data, len); size_t written = _content.write(data, len);
_contentLength += written; _contentLength += written;
return written; return written;

View file

@ -1,29 +1,12 @@
/* // SPDX-License-Identifier: LGPL-3.0-or-later
Asynchronous WebServer library for Espressif MCUs // 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 "ESPAsyncWebServer.h"
#include "WebHandlerImpl.h" #include "WebHandlerImpl.h"
using namespace asyncsrv; using namespace asyncsrv;
bool ON_STA_FILTER(AsyncWebServerRequest* request) { bool ON_STA_FILTER(AsyncWebServerRequest *request) {
#ifndef CONFIG_IDF_TARGET_ESP32H2 #ifndef CONFIG_IDF_TARGET_ESP32H2
return WiFi.localIP() == request->client()->localIP(); return WiFi.localIP() == request->client()->localIP();
#else #else
@ -31,7 +14,7 @@ bool ON_STA_FILTER(AsyncWebServerRequest* request) {
#endif #endif
} }
bool ON_AP_FILTER(AsyncWebServerRequest* request) { bool ON_AP_FILTER(AsyncWebServerRequest *request) {
#ifndef CONFIG_IDF_TARGET_ESP32H2 #ifndef CONFIG_IDF_TARGET_ESP32H2
return WiFi.localIP() != request->client()->localIP(); return WiFi.localIP() != request->client()->localIP();
#else #else
@ -40,51 +23,51 @@ bool ON_AP_FILTER(AsyncWebServerRequest* request) {
} }
#ifndef HAVE_FS_FILE_OPEN_MODE #ifndef HAVE_FS_FILE_OPEN_MODE
const char* fs::FileOpenMode::read = "r"; const char *fs::FileOpenMode::read = "r";
const char* fs::FileOpenMode::write = "w"; const char *fs::FileOpenMode::write = "w";
const char* fs::FileOpenMode::append = "a"; const char *fs::FileOpenMode::append = "a";
#endif #endif
AsyncWebServer::AsyncWebServer(uint16_t port) AsyncWebServer::AsyncWebServer(uint16_t port) : _server(port) {
: _server(port) {
_catchAllHandler = new AsyncCallbackWebHandler(); _catchAllHandler = new AsyncCallbackWebHandler();
if (_catchAllHandler == NULL) _server.onClient(
return; [](void *s, AsyncClient *c) {
_server.onClient([](void* s, AsyncClient* c) { if (c == NULL) {
if (c == NULL) return;
return; }
c->setRxTimeout(3); c->setRxTimeout(3);
AsyncWebServerRequest* r = new AsyncWebServerRequest((AsyncWebServer*)s, c); AsyncWebServerRequest *r = new AsyncWebServerRequest((AsyncWebServer *)s, c);
if (r == NULL) { if (r == NULL) {
c->abort(); c->abort();
delete c; delete c;
} }
}, },
this); this
);
} }
AsyncWebServer::~AsyncWebServer() { AsyncWebServer::~AsyncWebServer() {
reset(); reset();
end(); 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); _rewrites.emplace_back(rewrite);
return *_rewrites.back().get(); return *_rewrites.back().get();
} }
AsyncWebRewrite& AsyncWebServer::addRewrite(AsyncWebRewrite* rewrite) { AsyncWebRewrite &AsyncWebServer::addRewrite(AsyncWebRewrite *rewrite) {
_rewrites.emplace_back(rewrite); _rewrites.emplace_back(rewrite);
return *_rewrites.back().get(); return *_rewrites.back().get();
} }
bool AsyncWebServer::removeRewrite(AsyncWebRewrite* rewrite) { bool AsyncWebServer::removeRewrite(AsyncWebRewrite *rewrite) {
return removeRewrite(rewrite->from().c_str(), rewrite->toUrl().c_str()); 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) { for (auto r = _rewrites.begin(); r != _rewrites.end(); ++r) {
if (r->get()->from() == from && r->get()->toUrl() == to) { if (r->get()->from() == from && r->get()->toUrl() == to) {
_rewrites.erase(r); _rewrites.erase(r);
@ -94,17 +77,17 @@ bool AsyncWebServer::removeRewrite(const char* from, const char* to) {
return false; 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)); _rewrites.emplace_back(std::make_shared<AsyncWebRewrite>(from, to));
return *_rewrites.back().get(); return *_rewrites.back().get();
} }
AsyncWebHandler& AsyncWebServer::addHandler(AsyncWebHandler* handler) { AsyncWebHandler &AsyncWebServer::addHandler(AsyncWebHandler *handler) {
_handlers.emplace_back(handler); _handlers.emplace_back(handler);
return *(_handlers.back().get()); return *(_handlers.back().get());
} }
bool AsyncWebServer::removeHandler(AsyncWebHandler* handler) { bool AsyncWebServer::removeHandler(AsyncWebHandler *handler) {
for (auto i = _handlers.begin(); i != _handlers.end(); ++i) { for (auto i = _handlers.begin(); i != _handlers.end(); ++i) {
if (i->get() == handler) { if (i->get() == handler) {
_handlers.erase(i); _handlers.erase(i);
@ -124,21 +107,23 @@ void AsyncWebServer::end() {
} }
#if ASYNC_TCP_SSL_ENABLED #if ASYNC_TCP_SSL_ENABLED
void AsyncWebServer::onSslFileRequest(AcSSlFileHandler cb, void* arg) { void AsyncWebServer::onSslFileRequest(AcSSlFileHandler cb, void *arg) {
_server.onSslFileRequest(cb, 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); _server.beginSecure(cert, key, password);
} }
#endif #endif
void AsyncWebServer::_handleDisconnect(AsyncWebServerRequest* request) { void AsyncWebServer::_handleDisconnect(AsyncWebServerRequest *request) {
delete request; delete request;
} }
void AsyncWebServer::_rewriteRequest(AsyncWebServerRequest* request) { void AsyncWebServer::_rewriteRequest(AsyncWebServerRequest *request) {
for (const auto& r : _rewrites) { // 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)) { if (r->match(request)) {
request->_url = r->toUrl(); request->_url = r->toUrl();
request->_addGetParams(r->params()); request->_addGetParams(r->params());
@ -146,8 +131,8 @@ void AsyncWebServer::_rewriteRequest(AsyncWebServerRequest* request) {
} }
} }
void AsyncWebServer::_attachHandler(AsyncWebServerRequest* request) { void AsyncWebServer::_attachHandler(AsyncWebServerRequest *request) {
for (auto& h : _handlers) { for (auto &h : _handlers) {
if (h->filter(request) && h->canHandle(request)) { if (h->filter(request) && h->canHandle(request)) {
request->setHandler(h.get()); request->setHandler(h.get());
return; return;
@ -157,8 +142,10 @@ void AsyncWebServer::_attachHandler(AsyncWebServerRequest* request) {
request->setHandler(_catchAllHandler); request->setHandler(_catchAllHandler);
} }
AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody) { AsyncCallbackWebHandler &AsyncWebServer::on(
AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler(); const char *uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody
) {
AsyncCallbackWebHandler *handler = new AsyncCallbackWebHandler();
handler->setUri(uri); handler->setUri(uri);
handler->setMethod(method); handler->setMethod(method);
handler->onRequest(onRequest); handler->onRequest(onRequest);
@ -168,8 +155,8 @@ AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodCom
return *handler; return *handler;
} }
AsyncStaticWebHandler& AsyncWebServer::serveStatic(const char* uri, fs::FS& fs, const char* path, const char* 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); AsyncStaticWebHandler *handler = new AsyncStaticWebHandler(uri, fs, path, cache_control);
addHandler(handler); addHandler(handler);
return *handler; return *handler;
} }
@ -186,13 +173,15 @@ void AsyncWebServer::onRequestBody(ArBodyHandlerFunction fn) {
_catchAllHandler->onBody(fn); _catchAllHandler->onBody(fn);
} }
AsyncWebHandler &AsyncWebServer::catchAllHandler() const {
return *_catchAllHandler;
}
void AsyncWebServer::reset() { void AsyncWebServer::reset() {
_rewrites.clear(); _rewrites.clear();
_handlers.clear(); _handlers.clear();
if (_catchAllHandler != NULL) { _catchAllHandler->onRequest(NULL);
_catchAllHandler->onRequest(NULL); _catchAllHandler->onUpload(NULL);
_catchAllHandler->onUpload(NULL); _catchAllHandler->onBody(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 #pragma once
namespace asyncsrv { namespace asyncsrv {
static constexpr const char* empty = ""; static constexpr const char *empty = "";
static constexpr const char* T__opaque = "\", opaque=\""; static constexpr const char *T__opaque = "\", opaque=\"";
static constexpr const char* T_100_CONTINUE = "100-continue"; static constexpr const char *T_100_CONTINUE = "100-continue";
static constexpr const char* T_13 = "13"; static constexpr const char *T_13 = "13";
static constexpr const char* T_ACCEPT = "accept"; static constexpr const char *T_ACCEPT = "accept";
static constexpr const char* T_Accept_Ranges = "accept-ranges"; 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_app_xform_urlencoded = "application/x-www-form-urlencoded";
static constexpr const char* T_AUTH = "authorization"; static constexpr const char *T_AUTH = "authorization";
static constexpr const char* T_auth_nonce = "\", qop=\"auth\", nonce=\""; static constexpr const char *T_auth_nonce = "\", qop=\"auth\", nonce=\"";
static constexpr const char* T_BASIC = "basic"; static constexpr const char *T_BASIC = "basic";
static constexpr const char* T_BASIC_REALM = "basic realm=\""; static constexpr const char *T_BASIC_REALM = "basic realm=\"";
static constexpr const char* T_BEARER = "bearer"; static constexpr const char *T_BEARER = "bearer";
static constexpr const char* T_BODY = "body"; static constexpr const char *T_BODY = "body";
static constexpr const char* T_Cache_Control = "cache-control"; static constexpr const char *T_Cache_Control = "cache-control";
static constexpr const char* T_chunked = "chunked"; static constexpr const char *T_chunked = "chunked";
static constexpr const char* T_close = "close"; static constexpr const char *T_close = "close";
static constexpr const char* T_cnonce = "cnonce"; static constexpr const char *T_cnonce = "cnonce";
static constexpr const char* T_Connection = "connection"; static constexpr const char *T_Connection = "connection";
static constexpr const char* T_Content_Disposition = "content-disposition"; static constexpr const char *T_Content_Disposition = "content-disposition";
static constexpr const char* T_Content_Encoding = "content-encoding"; static constexpr const char *T_Content_Encoding = "content-encoding";
static constexpr const char* T_Content_Length = "content-length"; static constexpr const char *T_Content_Length = "content-length";
static constexpr const char* T_Content_Type = "content-type"; static constexpr const char *T_Content_Type = "content-type";
static constexpr const char* T_Cookie = "cookie"; static constexpr const char *T_Content_Location = "content-location";
static constexpr const char* T_CORS_ACAC = "access-control-allow-credentials"; static constexpr const char *T_Cookie = "cookie";
static constexpr const char* T_CORS_ACAH = "access-control-allow-headers"; static constexpr const char *T_CORS_ACAC = "access-control-allow-credentials";
static constexpr const char* T_CORS_ACAM = "access-control-allow-methods"; static constexpr const char *T_CORS_ACAH = "access-control-allow-headers";
static constexpr const char* T_CORS_ACAO = "access-control-allow-origin"; static constexpr const char *T_CORS_ACAM = "access-control-allow-methods";
static constexpr const char* T_CORS_ACMA = "access-control-max-age"; static constexpr const char *T_CORS_ACAO = "access-control-allow-origin";
static constexpr const char* T_CORS_O = "origin"; static constexpr const char *T_CORS_ACMA = "access-control-max-age";
static constexpr const char* T_data_ = "data: "; static constexpr const char *T_CORS_O = "origin";
static constexpr const char* T_DIGEST = "digest"; static constexpr const char *T_data_ = "data: ";
static constexpr const char* T_DIGEST_ = "digest "; static constexpr const char *T_Date = "date";
static constexpr const char* T_ETag = "etag"; static constexpr const char *T_DIGEST = "digest";
static constexpr const char* T_event_ = "event: "; static constexpr const char *T_DIGEST_ = "digest ";
static constexpr const char* T_EXPECT = "expect"; static constexpr const char *T_ETag = "etag";
static constexpr const char* T_FALSE = "false"; static constexpr const char *T_event_ = "event: ";
static constexpr const char* T_filename = "filename"; static constexpr const char *T_EXPECT = "expect";
static constexpr const char* T_gzip = "gzip"; static constexpr const char *T_FALSE = "false";
static constexpr const char* T_Host = "host"; static constexpr const char *T_filename = "filename";
static constexpr const char* T_HTTP_1_0 = "HTTP/1.0"; static constexpr const char *T_gzip = "gzip";
static constexpr const char* T_HTTP_100_CONT = "HTTP/1.1 100 Continue\r\n\r\n"; static constexpr const char *T_Host = "host";
static constexpr const char* T_id__ = "id: "; static constexpr const char *T_HTTP_1_0 = "HTTP/1.0";
static constexpr const char* T_IMS = "if-modified-since"; static constexpr const char *T_HTTP_100_CONT = "HTTP/1.1 100 Continue\r\n\r\n";
static constexpr const char* T_INM = "if-none-match"; static constexpr const char *T_id__ = "id: ";
static constexpr const char* T_keep_alive = "keep-alive"; static constexpr const char *T_IMS = "if-modified-since";
static constexpr const char* T_Last_Event_ID = "last-event-id"; static constexpr const char *T_INM = "if-none-match";
static constexpr const char* T_Last_Modified = "last-modified"; static constexpr const char *T_keep_alive = "keep-alive";
static constexpr const char* T_LOCATION = "location"; static constexpr const char *T_Last_Event_ID = "last-event-id";
static constexpr const char* T_LOGIN_REQ = "Login Required"; static constexpr const char *T_Last_Modified = "last-modified";
static constexpr const char* T_MULTIPART_ = "multipart/"; static constexpr const char *T_LOCATION = "location";
static constexpr const char* T_name = "name"; static constexpr const char *T_LOGIN_REQ = "Login Required";
static constexpr const char* T_nc = "nc"; static constexpr const char *T_MULTIPART_ = "multipart/";
static constexpr const char* T_no_cache = "no-cache"; static constexpr const char *T_name = "name";
static constexpr const char* T_nonce = "nonce"; static constexpr const char *T_nc = "nc";
static constexpr const char* T_none = "none"; static constexpr const char *T_no_cache = "no-cache";
static constexpr const char* T_opaque = "opaque"; static constexpr const char *T_nonce = "nonce";
static constexpr const char* T_qop = "qop"; static constexpr const char *T_none = "none";
static constexpr const char* T_realm = "realm"; static constexpr const char *T_opaque = "opaque";
static constexpr const char* T_realm__ = "realm=\""; static constexpr const char *T_qop = "qop";
static constexpr const char* T_response = "response"; static constexpr const char *T_realm = "realm";
static constexpr const char* T_retry_ = "retry: "; static constexpr const char *T_realm__ = "realm=\"";
static constexpr const char* T_retry_after = "retry-after"; static constexpr const char *T_response = "response";
static constexpr const char* T_nn = "\n\n"; static constexpr const char *T_retry_ = "retry: ";
static constexpr const char* T_rn = "\r\n"; static constexpr const char *T_retry_after = "retry-after";
static constexpr const char* T_rnrn = "\r\n\r\n"; static constexpr const char *T_nn = "\n\n";
static constexpr const char* T_Transfer_Encoding = "transfer-encoding"; static constexpr const char *T_rn = "\r\n";
static constexpr const char* T_TRUE = "true"; static constexpr const char *T_rnrn = "\r\n\r\n";
static constexpr const char* T_UPGRADE = "upgrade"; static constexpr const char *T_Server = "server";
static constexpr const char* T_uri = "uri"; static constexpr const char *T_Transfer_Encoding = "transfer-encoding";
static constexpr const char* T_username = "username"; static constexpr const char *T_TRUE = "true";
static constexpr const char* T_WS = "websocket"; static constexpr const char *T_UPGRADE = "upgrade";
static constexpr const char* T_WWW_AUTH = "www-authenticate"; 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_ANY = "ANY";
static constexpr const char* T_GET = "GET"; static constexpr const char *T_GET = "GET";
static constexpr const char* T_POST = "POST"; static constexpr const char *T_POST = "POST";
static constexpr const char* T_PUT = "PUT"; static constexpr const char *T_PUT = "PUT";
static constexpr const char* T_DELETE = "DELETE"; static constexpr const char *T_DELETE = "DELETE";
static constexpr const char* T_PATCH = "PATCH"; static constexpr const char *T_PATCH = "PATCH";
static constexpr const char* T_HEAD = "HEAD"; static constexpr const char *T_HEAD = "HEAD";
static constexpr const char* T_OPTIONS = "OPTIONS"; static constexpr const char *T_OPTIONS = "OPTIONS";
static constexpr const char* T_UNKNOWN = "UNKNOWN"; static constexpr const char *T_UNKNOWN = "UNKNOWN";
// Req content types // Req content types
static constexpr const char* T_RCT_NOT_USED = "RCT_NOT_USED"; 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_DEFAULT = "RCT_DEFAULT";
static constexpr const char* T_RCT_HTTP = "RCT_HTTP"; static constexpr const char *T_RCT_HTTP = "RCT_HTTP";
static constexpr const char* T_RCT_WS = "RCT_WS"; static constexpr const char *T_RCT_WS = "RCT_WS";
static constexpr const char* T_RCT_EVENT = "RCT_EVENT"; static constexpr const char *T_RCT_EVENT = "RCT_EVENT";
static constexpr const char* T_ERROR = "ERROR"; static constexpr const char *T_ERROR = "ERROR";
// extentions & MIME-Types // extensions & MIME-Types
static constexpr const char* T__css = ".css"; static constexpr const char *T__css = ".css";
static constexpr const char* T__eot = ".eot"; static constexpr const char *T__eot = ".eot";
static constexpr const char* T__gif = ".gif"; static constexpr const char *T__gif = ".gif";
static constexpr const char* T__gz = ".gz"; static constexpr const char *T__gz = ".gz";
static constexpr const char* T__htm = ".htm"; static constexpr const char *T__htm = ".htm";
static constexpr const char* T__html = ".html"; static constexpr const char *T__html = ".html";
static constexpr const char* T__ico = ".ico"; static constexpr const char *T__ico = ".ico";
static constexpr const char* T__jpg = ".jpg"; static constexpr const char *T__jpg = ".jpg";
static constexpr const char* T__js = ".js"; static constexpr const char *T__js = ".js";
static constexpr const char* T__json = ".json"; static constexpr const char *T__json = ".json";
static constexpr const char* T__pdf = ".pdf"; static constexpr const char *T__pdf = ".pdf";
static constexpr const char* T__png = ".png"; static constexpr const char *T__png = ".png";
static constexpr const char* T__svg = ".svg"; static constexpr const char *T__svg = ".svg";
static constexpr const char* T__ttf = ".ttf"; static constexpr const char *T__ttf = ".ttf";
static constexpr const char* T__woff = ".woff"; static constexpr const char *T__woff = ".woff";
static constexpr const char* T__woff2 = ".woff2"; static constexpr const char *T__woff2 = ".woff2";
static constexpr const char* T__xml = ".xml"; static constexpr const char *T__xml = ".xml";
static constexpr const char* T__zip = ".zip"; static constexpr const char *T__zip = ".zip";
static constexpr const char* T_application_javascript = "application/javascript"; static constexpr const char *T_application_javascript = "application/javascript";
static constexpr const char* T_application_json = "application/json"; static constexpr const char *T_application_json = "application/json";
static constexpr const char* T_application_msgpack = "application/msgpack"; static constexpr const char *T_application_msgpack = "application/msgpack";
static constexpr const char* T_application_pdf = "application/pdf"; 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_x_gzip = "application/x-gzip";
static constexpr const char* T_application_zip = "application/zip"; static constexpr const char *T_application_zip = "application/zip";
static constexpr const char* T_font_eot = "font/eot"; static constexpr const char *T_font_eot = "font/eot";
static constexpr const char* T_font_ttf = "font/ttf"; static constexpr const char *T_font_ttf = "font/ttf";
static constexpr const char* T_font_woff = "font/woff"; static constexpr const char *T_font_woff = "font/woff";
static constexpr const char* T_font_woff2 = "font/woff2"; static constexpr const char *T_font_woff2 = "font/woff2";
static constexpr const char* T_image_gif = "image/gif"; static constexpr const char *T_image_gif = "image/gif";
static constexpr const char* T_image_jpeg = "image/jpeg"; static constexpr const char *T_image_jpeg = "image/jpeg";
static constexpr const char* T_image_png = "image/png"; 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_svg_xml = "image/svg+xml";
static constexpr const char* T_image_x_icon = "image/x-icon"; 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_css = "text/css";
static constexpr const char* T_text_event_stream = "text/event-stream"; 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_html = "text/html";
static constexpr const char* T_text_plain = "text/plain"; static constexpr const char *T_text_plain = "text/plain";
static constexpr const char* T_text_xml = "text/xml"; static constexpr const char *T_text_xml = "text/xml";
// Responce codes // Response codes
static constexpr const char* T_HTTP_CODE_100 = "Continue"; 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_101 = "Switching Protocols";
static constexpr const char* T_HTTP_CODE_200 = "OK"; 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_201 = "Created";
static constexpr const char* T_HTTP_CODE_202 = "Accepted"; 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_203 = "Non-Authoritative Information";
static constexpr const char* T_HTTP_CODE_204 = "No Content"; 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_205 = "Reset Content";
static constexpr const char* T_HTTP_CODE_206 = "Partial 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_300 = "Multiple Choices";
static constexpr const char* T_HTTP_CODE_301 = "Moved Permanently"; 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_302 = "Found";
static constexpr const char* T_HTTP_CODE_303 = "See Other"; 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_304 = "Not Modified";
static constexpr const char* T_HTTP_CODE_305 = "Use Proxy"; 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_307 = "Temporary Redirect";
static constexpr const char* T_HTTP_CODE_400 = "Bad Request"; 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_401 = "Unauthorized";
static constexpr const char* T_HTTP_CODE_402 = "Payment Required"; 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_403 = "Forbidden";
static constexpr const char* T_HTTP_CODE_404 = "Not Found"; 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_405 = "Method Not Allowed";
static constexpr const char* T_HTTP_CODE_406 = "Not Acceptable"; 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_407 = "Proxy Authentication Required";
static constexpr const char* T_HTTP_CODE_408 = "Request Time-out"; 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_409 = "Conflict";
static constexpr const char* T_HTTP_CODE_410 = "Gone"; 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_411 = "Length Required";
static constexpr const char* T_HTTP_CODE_412 = "Precondition Failed"; 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_413 = "Request Entity Too Large";
static constexpr const char* T_HTTP_CODE_414 = "Request-URI 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_415 = "Unsupported Media Type";
static constexpr const char* T_HTTP_CODE_416 = "Requested range not satisfiable"; static constexpr const char *T_HTTP_CODE_416 = "Requested range not satisfiable";
static constexpr const char* T_HTTP_CODE_417 = "Expectation Failed"; static constexpr const char *T_HTTP_CODE_417 = "Expectation Failed";
static constexpr const char* T_HTTP_CODE_429 = "Too Many Requests"; 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_500 = "Internal Server Error";
static constexpr const char* T_HTTP_CODE_501 = "Not Implemented"; 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_502 = "Bad Gateway";
static constexpr const char* T_HTTP_CODE_503 = "Service Unavailable"; 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_504 = "Gateway Time-out";
static constexpr const char* T_HTTP_CODE_505 = "HTTP Version not supported"; static constexpr const char *T_HTTP_CODE_505 = "HTTP Version not supported";
static constexpr const char* T_HTTP_CODE_ANY = "Unknown code"; static constexpr const 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 "Update.h"
#include "StreamString.h" #include "StreamString.h"
#if ELEGANTOTA_USE_ASYNC_WEBSERVER == 1 #if ELEGANTOTA_USE_ASYNC_WEBSERVER == 1
#include "../../ESP32Async-AsyncTCP/src/AsyncTCP.h" #include "../../mathieucarbou-AsyncTCPSock/src/AsyncTCP.h"
#include "../../ESP32Async-ESPAsyncWebServer/src/ESPAsyncWebServer.h" #include "../../ESP32Async-ESPAsyncWebServer/src/ESPAsyncWebServer.h"
#define ELEGANTOTA_WEBSERVER AsyncWebServer #define ELEGANTOTA_WEBSERVER AsyncWebServer
#else #else

View file

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

View file

@ -93,6 +93,10 @@ typedef struct {
uint8_t AMR3; /**< \brief Acceptance Mask Register AMR3 */ uint8_t AMR3; /**< \brief Acceptance Mask Register AMR3 */
} CAN_filter_t; } 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 * \brief Initialize the CAN Module
* *
@ -104,9 +108,9 @@ int CAN_init(void);
* \brief Send a can frame * \brief Send a can frame
* *
* \param p_frame Pointer to the frame to be send, see #CAN_frame_t * \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 * \brief Stops the CAN Module

View file

@ -5,28 +5,10 @@
int ESP32CAN::CANInit() { int ESP32CAN::CANInit() {
return CAN_init(); return CAN_init();
} }
int ESP32CAN::CANWriteFrame(const CAN_frame_t* p_frame) { bool ESP32CAN::CANWriteFrame(const CAN_frame_t* p_frame) {
static unsigned long start_time; return CAN_write_frame(p_frame);
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;
} }
int ESP32CAN::CANStop() { int ESP32CAN::CANStop() {
return CAN_stop(); return CAN_stop();
} }

View file

@ -9,7 +9,7 @@ class ESP32CAN {
bool tx_ok = true; bool tx_ok = true;
int CANInit(); int CANInit();
int CANConfigFilter(const CAN_filter_t* p_filter); 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(); int CANStop();
void CANSetCfg(CAN_device_t* can_cfg); void CANSetCfg(CAN_device_t* can_cfg);
}; };