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,22 +1,6 @@
/* // 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>
@ -30,15 +14,22 @@ 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_;
@ -58,8 +49,9 @@ static String generateEventMessage(const char* message, const char* event, uint3
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;
@ -79,7 +71,7 @@ static String generateEventMessage(const char* message, const char* event, uint3
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 {
@ -124,8 +116,9 @@ size_t AsyncEventSourceMessage::ack(size_t len, __attribute__((unused)) uint32_t
} }
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;
@ -155,19 +148,42 @@ size_t AsyncEventSourceMessage::send(AsyncClient* client) {
// 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;
@ -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,9 +281,10 @@ 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() {
if (_messageQueue.size()) { if (_messageQueue.size()) {
@ -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,14 +343,16 @@ 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 */
@ -338,27 +363,32 @@ void AsyncEventSource::authorizeConnect(ArAuthorizeConnectHandler cb) {
} }
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();
} }
@ -371,10 +401,11 @@ void AsyncEventSource::close() {
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();
} }
} }
}
// pmb fix // pmb fix
size_t AsyncEventSource::avgPacketsWaiting() const { size_t AsyncEventSource::avgPacketsWaiting() const {
@ -383,8 +414,9 @@ 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()) {
@ -395,8 +427,7 @@ size_t AsyncEventSource::avgPacketsWaiting() const {
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);
@ -404,11 +435,12 @@ AsyncEventSource::SendStatus AsyncEventSource::send(
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,9 +449,11 @@ 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;
} }
@ -435,8 +469,9 @@ void AsyncEventSource::handleRequest(AsyncWebServerRequest* request) {
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);
} }
} }

View file

@ -1,29 +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 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
@ -37,8 +21,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
@ -76,18 +60,26 @@ class AsyncEventSourceMessage {
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
*/ */
@ -111,13 +103,17 @@ class AsyncEventSourceMessage {
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();
}
}; };
/** /**
@ -155,8 +151,12 @@ class AsyncEventSourceClient {
* @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
@ -166,20 +166,32 @@ 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
@ -195,7 +207,9 @@ class AsyncEventSourceClient {
* *
* @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);
@ -233,9 +247,13 @@ class AsyncEventSource : public AsyncWebHandler {
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 {
return _url.c_str();
}
// close all connected clients // close all connected clients
void close(); void close();
@ -245,7 +263,9 @@ class AsyncEventSource : public AsyncWebHandler {
* *
* @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
@ -258,11 +278,17 @@ class AsyncEventSource : public AsyncWebHandler {
* @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) {
_disconnectcb = cb;
}
void authorizeConnect(ArAuthorizeConnectHandler cb); void authorizeConnect(ArAuthorizeConnectHandler cb);
// returns number of connected clients // returns number of connected clients
@ -286,7 +312,9 @@ class AsyncEventSourceResponse : public AsyncWebServerResponse {
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,3 +1,6 @@
// 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
@ -6,29 +9,32 @@
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() {
@ -90,14 +96,17 @@ AsyncCallbackJsonWebHandler::AsyncCallbackJsonWebHandler(const String& uri, ArJs
#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;
} }
@ -141,6 +150,13 @@ void AsyncCallbackJsonWebHandler::handleBody(AsyncWebServerRequest* request, uin
_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);

View file

@ -1,37 +1,6 @@
// 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_
@ -74,13 +43,21 @@ class AsyncJsonResponse : public AsyncAbstractResponse {
#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;
}
bool _sourceValid() const {
return _isValid;
}
size_t setLength(); size_t setLength();
size_t getSize() const { return _jsonBuffer.size(); } size_t getSize() const {
return _jsonBuffer.size();
}
size_t _fillBuffer(uint8_t *data, size_t len); size_t _fillBuffer(uint8_t *data, size_t len);
#if ARDUINOJSON_VERSION_MAJOR >= 6 #if ARDUINOJSON_VERSION_MAJOR >= 6
bool overflowed() const { return _jsonBuffer.overflowed(); } bool overflowed() const {
return _jsonBuffer.overflowed();
}
#endif #endif
}; };
@ -115,15 +92,26 @@ class AsyncCallbackJsonWebHandler : public AsyncWebHandler {
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(
__unused AsyncWebServerRequest *request, __unused const String &filename, __unused size_t index, __unused uint8_t *data, __unused size_t len,
__unused bool final
) override final {}
void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override final; 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 // ASYNC_JSON_SUPPORT == 1 #endif // ASYNC_JSON_SUPPORT == 1

View file

@ -1,3 +1,6 @@
// 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
@ -6,20 +9,22 @@
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() {
@ -37,7 +42,9 @@ size_t AsyncMessagePackResponse::_fillBuffer(uint8_t* data, size_t len) {
} }
#if ARDUINOJSON_VERSION_MAJOR == 6 #if ARDUINOJSON_VERSION_MAJOR == 6
AsyncCallbackMessagePackWebHandler::AsyncCallbackMessagePackWebHandler(const String& uri, ArMessagePackRequestHandlerFunction onRequest, size_t maxJsonBufferSize) AsyncCallbackMessagePackWebHandler::AsyncCallbackMessagePackWebHandler(
const String &uri, ArMessagePackRequestHandlerFunction onRequest, size_t maxJsonBufferSize
)
: _uri(uri), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), maxJsonBufferSize(maxJsonBufferSize), _maxContentLength(16384) {} : _uri(uri), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), maxJsonBufferSize(maxJsonBufferSize), _maxContentLength(16384) {}
#else #else
AsyncCallbackMessagePackWebHandler::AsyncCallbackMessagePackWebHandler(const String &uri, ArMessagePackRequestHandlerFunction onRequest) AsyncCallbackMessagePackWebHandler::AsyncCallbackMessagePackWebHandler(const String &uri, ArMessagePackRequestHandlerFunction onRequest)
@ -45,14 +52,17 @@ AsyncCallbackMessagePackWebHandler::AsyncCallbackMessagePackWebHandler(const Str
#endif #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;
} }
@ -92,6 +102,13 @@ void AsyncCallbackMessagePackWebHandler::handleBody(AsyncWebServerRequest* reque
_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);

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
/* /*
@ -58,13 +61,21 @@ class AsyncMessagePackResponse : public AsyncAbstractResponse {
#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;
}
bool _sourceValid() const {
return _isValid;
}
size_t setLength(); size_t setLength();
size_t getSize() const { return _jsonBuffer.size(); } size_t getSize() const {
return _jsonBuffer.size();
}
size_t _fillBuffer(uint8_t *data, size_t len); size_t _fillBuffer(uint8_t *data, size_t len);
#if ARDUINOJSON_VERSION_MAJOR >= 6 #if ARDUINOJSON_VERSION_MAJOR >= 6
bool overflowed() const { return _jsonBuffer.overflowed(); } bool overflowed() const {
return _jsonBuffer.overflowed();
}
#endif #endif
}; };
@ -83,20 +94,33 @@ class AsyncCallbackMessagePackWebHandler : public AsyncWebHandler {
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(
const String &uri, ArMessagePackRequestHandlerFunction onRequest = nullptr, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE
);
#else #else
AsyncCallbackMessagePackWebHandler(const String &uri, ArMessagePackRequestHandlerFunction onRequest = nullptr); AsyncCallbackMessagePackWebHandler(const String &uri, ArMessagePackRequestHandlerFunction onRequest = nullptr);
#endif #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(
__unused AsyncWebServerRequest *request, __unused const String &filename, __unused size_t index, __unused uint8_t *data, __unused size_t len,
__unused bool final
) override final {}
void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override final; 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 // 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,23 +1,6 @@
/* // 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 "AsyncWebSocket.h" #include "AsyncWebSocket.h"
#include "Arduino.h" #include "Arduino.h"
@ -32,18 +15,20 @@
#include <SHA1Builder.h> #include <SHA1Builder.h>
#endif #endif
#include <rom/ets_sys.h> #include <rom/ets_sys.h>
#elif defined(TARGET_RP2040) || defined(ESP8266) #elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) || defined(ESP8266)
#include <Hash.h> #include <Hash.h>
#endif #endif
using namespace asyncsrv; using namespace asyncsrv;
size_t webSocketSendFrameWindow(AsyncClient *client) { size_t webSocketSendFrameWindow(AsyncClient *client) {
if (!client || !client->canSend()) if (!client || !client->canSend()) {
return 0; return 0;
}
size_t space = client->space(); size_t space = client->space();
if (space < 9) if (space < 9) {
return 0; return 0;
}
return space - 8; return space - 8;
} }
@ -66,30 +51,35 @@ size_t webSocketSendFrame(AsyncClient* client, bool final, uint8_t opcode, bool
mbuf[2] = rand() % 0xFF; mbuf[2] = rand() % 0xFF;
mbuf[3] = rand() % 0xFF; mbuf[3] = rand() % 0xFF;
} }
if (len > 125) if (len > 125) {
headLen += 2; headLen += 2;
}
if (space < headLen) { if (space < headLen) {
// Serial.println("SF 2"); // Serial.println("SF 2");
return 0; return 0;
} }
space -= headLen; space -= headLen;
if (len > space) if (len > space) {
len = space; len = space;
}
uint8_t *buf = (uint8_t *)malloc(headLen); uint8_t *buf = (uint8_t *)malloc(headLen);
if (buf == NULL) { if (buf == NULL) {
// os_printf("could not malloc %u bytes for frame header\n", headLen); #ifdef ESP32
// Serial.println("SF 3"); log_e("Failed to allocate");
#endif
client->abort();
return 0; return 0;
} }
buf[0] = opcode & 0x0F; buf[0] = opcode & 0x0F;
if (final) if (final) {
buf[0] |= 0x80; buf[0] |= 0x80;
if (len < 126) }
if (len < 126) {
buf[1] = len & 0x7F; buf[1] = len & 0x7F;
else { } else {
buf[1] = 126; buf[1] = 126;
buf[2] = (uint8_t)((len >> 8) & 0xFF); buf[2] = (uint8_t)((len >> 8) & 0xFF);
buf[3] = (uint8_t)(len & 0xFF); buf[3] = (uint8_t)(len & 0xFF);
@ -109,9 +99,10 @@ size_t webSocketSendFrame(AsyncClient* client, bool final, uint8_t opcode, bool
if (len) { if (len) {
if (len && mask) { if (len && mask) {
size_t i; size_t i;
for (i = 0; i < len; i++) for (i = 0; i < len; i++) {
data[i] = data[i] ^ mbuf[i % 4]; data[i] = data[i] ^ mbuf[i % 4];
} }
}
if (client->add((const char *)data, len) != len) { if (client->add((const char *)data, len) != len) {
// os_printf("error adding %lu data bytes\n", len); // os_printf("error adding %lu data bytes\n", len);
// Serial.println("SF 5"); // Serial.println("SF 5");
@ -131,8 +122,7 @@ size_t webSocketSendFrame(AsyncClient* client, bool final, uint8_t opcode, bool
* AsyncWebSocketMessageBuffer * AsyncWebSocketMessageBuffer
*/ */
AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(const uint8_t* data, size_t size) AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(const uint8_t *data, size_t size) : _buffer(std::make_shared<std::vector<uint8_t>>(size)) {
: _buffer(std::make_shared<std::vector<uint8_t>>(size)) {
if (_buffer->capacity() < size) { if (_buffer->capacity() < size) {
_buffer->reserve(size); _buffer->reserve(size);
} else { } else {
@ -140,16 +130,16 @@ AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(const uint8_t* data, si
} }
} }
AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(size_t size) AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(size_t size) : _buffer(std::make_shared<std::vector<uint8_t>>(size)) {
: _buffer(std::make_shared<std::vector<uint8_t>>(size)) {
if (_buffer->capacity() < size) { if (_buffer->capacity() < size) {
_buffer->reserve(size); _buffer->reserve(size);
} }
} }
bool AsyncWebSocketMessageBuffer::reserve(size_t size) { bool AsyncWebSocketMessageBuffer::reserve(size_t size) {
if (_buffer->capacity() >= size) if (_buffer->capacity() >= size) {
return true; return true;
}
_buffer->reserve(size); _buffer->reserve(size);
return _buffer->capacity() >= size; return _buffer->capacity() >= size;
} }
@ -169,30 +159,44 @@ class AsyncWebSocketControl {
public: public:
AsyncWebSocketControl(uint8_t opcode, const uint8_t *data = NULL, size_t len = 0, bool mask = false) AsyncWebSocketControl(uint8_t opcode, const uint8_t *data = NULL, size_t len = 0, bool mask = false)
: _opcode(opcode), _len(len), _mask(len && mask), _finished(false) { : _opcode(opcode), _len(len), _mask(len && mask), _finished(false) {
if (data == NULL) if (data == NULL) {
_len = 0; _len = 0;
}
if (_len) { if (_len) {
if (_len > 125) if (_len > 125) {
_len = 125; _len = 125;
}
_data = (uint8_t *)malloc(_len); _data = (uint8_t *)malloc(_len);
if (_data == NULL) if (_data == NULL) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
_len = 0; _len = 0;
else } else {
memcpy(_data, data, len); memcpy(_data, data, len);
} else }
} else {
_data = NULL; _data = NULL;
} }
}
~AsyncWebSocketControl() { ~AsyncWebSocketControl() {
if (_data != NULL) if (_data != NULL) {
free(_data); free(_data);
} }
}
bool finished() const { return _finished; } bool finished() const {
uint8_t opcode() { return _opcode; } return _finished;
uint8_t len() { return _len + 2; } }
uint8_t opcode() {
return _opcode;
}
uint8_t len() {
return _len + 2;
}
size_t send(AsyncClient *client) { size_t send(AsyncClient *client) {
_finished = true; _finished = true;
return webSocketSendFrame(client, true, _opcode & 0x0F, _mask, _data, _len); return webSocketSendFrame(client, true, _opcode & 0x0F, _mask, _data, _len);
@ -203,11 +207,8 @@ class AsyncWebSocketControl {
* AsyncWebSocketMessage Message * AsyncWebSocketMessage Message
*/ */
AsyncWebSocketMessage::AsyncWebSocketMessage(AsyncWebSocketSharedBuffer buffer, uint8_t opcode, bool mask) : _WSbuffer{buffer}, AsyncWebSocketMessage::AsyncWebSocketMessage(AsyncWebSocketSharedBuffer buffer, uint8_t opcode, bool mask)
_opcode(opcode & 0x07), : _WSbuffer{buffer}, _opcode(opcode & 0x07), _mask{mask}, _status{_WSbuffer ? WS_MSG_SENDING : WS_MSG_ERROR} {}
_mask{mask},
_status{_WSbuffer ? WS_MSG_SENDING : WS_MSG_ERROR} {
}
void AsyncWebSocketMessage::ack(size_t len, uint32_t time) { void AsyncWebSocketMessage::ack(size_t len, uint32_t time) {
(void)time; (void)time;
@ -219,17 +220,20 @@ void AsyncWebSocketMessage::ack(size_t len, uint32_t time) {
} }
size_t AsyncWebSocketMessage::send(AsyncClient *client) { size_t AsyncWebSocketMessage::send(AsyncClient *client) {
if (!client) if (!client) {
return 0; return 0;
}
if (_status != WS_MSG_SENDING) if (_status != WS_MSG_SENDING) {
return 0; return 0;
}
if (_acked < _ack) { if (_acked < _ack) {
return 0; return 0;
} }
if (_sent == _WSbuffer->size()) { if (_sent == _WSbuffer->size()) {
if (_acked == _ack) if (_acked == _ack) {
_status = WS_MSG_SENT; _status = WS_MSG_SENT;
}
return 0; return 0;
} }
if (_sent > _WSbuffer->size()) { if (_sent > _WSbuffer->size()) {
@ -271,8 +275,7 @@ size_t AsyncWebSocketMessage::send(AsyncClient* client) {
const char *AWSC_PING_PAYLOAD = "ESPAsyncWebServer-PING"; const char *AWSC_PING_PAYLOAD = "ESPAsyncWebServer-PING";
const size_t AWSC_PING_PAYLOAD_LEN = 22; const size_t AWSC_PING_PAYLOAD_LEN = 22;
AsyncWebSocketClient::AsyncWebSocketClient(AsyncWebServerRequest* request, AsyncWebSocket* server) AsyncWebSocketClient::AsyncWebSocketClient(AsyncWebServerRequest *request, AsyncWebSocket *server) : _tempObject(NULL) {
: _tempObject(NULL) {
_client = request->client(); _client = request->client();
_server = server; _server = server;
_clientId = _server->_getNextId(); _clientId = _server->_getNextId();
@ -281,12 +284,48 @@ AsyncWebSocketClient::AsyncWebSocketClient(AsyncWebServerRequest* request, Async
_lastMessageTime = millis(); _lastMessageTime = millis();
_keepAlivePeriod = 0; _keepAlivePeriod = 0;
_client->setRxTimeout(0); _client->setRxTimeout(0);
_client->onError([](void* r, AsyncClient* c, int8_t error) { (void)c; ((AsyncWebSocketClient*)(r))->_onError(error); }, this); _client->onError(
_client->onAck([](void* r, AsyncClient* c, size_t len, uint32_t time) { (void)c; ((AsyncWebSocketClient*)(r))->_onAck(len, time); }, this); [](void *r, AsyncClient *c, int8_t error) {
_client->onDisconnect([](void* r, AsyncClient* c) { ((AsyncWebSocketClient*)(r))->_onDisconnect(); delete c; }, this); (void)c;
_client->onTimeout([](void* r, AsyncClient* c, uint32_t time) { (void)c; ((AsyncWebSocketClient*)(r))->_onTimeout(time); }, this); ((AsyncWebSocketClient *)(r))->_onError(error);
_client->onData([](void* r, AsyncClient* c, void* buf, size_t len) { (void)c; ((AsyncWebSocketClient*)(r))->_onData(buf, len); }, this); },
_client->onPoll([](void* r, AsyncClient* c) { (void)c; ((AsyncWebSocketClient*)(r))->_onPoll(); }, this); this
);
_client->onAck(
[](void *r, AsyncClient *c, size_t len, uint32_t time) {
(void)c;
((AsyncWebSocketClient *)(r))->_onAck(len, time);
},
this
);
_client->onDisconnect(
[](void *r, AsyncClient *c) {
((AsyncWebSocketClient *)(r))->_onDisconnect();
delete c;
},
this
);
_client->onTimeout(
[](void *r, AsyncClient *c, uint32_t time) {
(void)c;
((AsyncWebSocketClient *)(r))->_onTimeout(time);
},
this
);
_client->onData(
[](void *r, AsyncClient *c, void *buf, size_t len) {
(void)c;
((AsyncWebSocketClient *)(r))->_onData(buf, len);
},
this
);
_client->onPoll(
[](void *r, AsyncClient *c) {
(void)c;
((AsyncWebSocketClient *)(r))->_onPoll();
},
this
);
delete request; delete request;
memset(&_pinfo, 0, sizeof(_pinfo)); memset(&_pinfo, 0, sizeof(_pinfo));
} }
@ -303,9 +342,10 @@ AsyncWebSocketClient::~AsyncWebSocketClient() {
} }
void AsyncWebSocketClient::_clearQueue() { void AsyncWebSocketClient::_clearQueue() {
while (!_messageQueue.empty() && _messageQueue.front().finished()) while (!_messageQueue.empty() && _messageQueue.front().finished()) {
_messageQueue.pop_front(); _messageQueue.pop_front();
} }
}
void AsyncWebSocketClient::_onAck(size_t len, uint32_t time) { void AsyncWebSocketClient::_onAck(size_t len, uint32_t time) {
_lastMessageTime = millis(); _lastMessageTime = millis();
@ -321,8 +361,9 @@ void AsyncWebSocketClient::_onAck(size_t len, uint32_t time) {
if (_status == WS_DISCONNECTING && head.opcode() == WS_DISCONNECT) { if (_status == WS_DISCONNECTING && head.opcode() == WS_DISCONNECT) {
_controlQueue.pop_front(); _controlQueue.pop_front();
_status = WS_DISCONNECTED; _status = WS_DISCONNECTED;
if (_client) if (_client) {
_client->close(true); _client->close(true);
}
return; return;
} }
_controlQueue.pop_front(); _controlQueue.pop_front();
@ -339,8 +380,9 @@ void AsyncWebSocketClient::_onAck(size_t len, uint32_t time) {
} }
void AsyncWebSocketClient::_onPoll() { void AsyncWebSocketClient::_onPoll() {
if (!_client) if (!_client) {
return; return;
}
#ifdef ESP32 #ifdef ESP32
std::unique_lock<std::mutex> lock(_lock); std::unique_lock<std::mutex> lock(_lock);
@ -357,12 +399,14 @@ void AsyncWebSocketClient::_onPoll() {
void AsyncWebSocketClient::_runQueue() { void AsyncWebSocketClient::_runQueue() {
// all calls to this method MUST be protected by a mutex lock! // all calls to this method MUST be protected by a mutex lock!
if (!_client) if (!_client) {
return; return;
}
_clearQueue(); _clearQueue();
if (!_controlQueue.empty() && (_messageQueue.empty() || _messageQueue.front().betweenFrames()) && webSocketSendFrameWindow(_client) > (size_t)(_controlQueue.front().len() - 1)) { if (!_controlQueue.empty() && (_messageQueue.empty() || _messageQueue.front().betweenFrames())
&& webSocketSendFrameWindow(_client) > (size_t)(_controlQueue.front().len() - 1)) {
_controlQueue.front().send(_client); _controlQueue.front().send(_client);
} else if (!_messageQueue.empty() && _messageQueue.front().betweenFrames() && webSocketSendFrameWindow(_client)) { } else if (!_messageQueue.empty() && _messageQueue.front().betweenFrames() && webSocketSendFrameWindow(_client)) {
_messageQueue.front().send(_client); _messageQueue.front().send(_client);
@ -391,8 +435,9 @@ bool AsyncWebSocketClient::canSend() const {
} }
bool AsyncWebSocketClient::_queueControl(uint8_t opcode, const uint8_t *data, size_t len, bool mask) { bool AsyncWebSocketClient::_queueControl(uint8_t opcode, const uint8_t *data, size_t len, bool mask) {
if (!_client) if (!_client) {
return false; return false;
}
#ifdef ESP32 #ifdef ESP32
std::lock_guard<std::mutex> lock(_lock); std::lock_guard<std::mutex> lock(_lock);
@ -400,15 +445,17 @@ bool AsyncWebSocketClient::_queueControl(uint8_t opcode, const uint8_t* data, si
_controlQueue.emplace_back(opcode, data, len, mask); _controlQueue.emplace_back(opcode, data, len, mask);
if (_client && _client->canSend()) if (_client && _client->canSend()) {
_runQueue(); _runQueue();
}
return true; return true;
} }
bool AsyncWebSocketClient::_queueMessage(AsyncWebSocketSharedBuffer buffer, uint8_t opcode, bool mask) { bool AsyncWebSocketClient::_queueMessage(AsyncWebSocketSharedBuffer buffer, uint8_t opcode, bool mask) {
if (!_client || buffer->size() == 0 || _status != WS_CONNECTED) if (!_client || buffer->size() == 0 || _status != WS_CONNECTED) {
return false; return false;
}
#ifdef ESP32 #ifdef ESP32
std::lock_guard<std::mutex> lock(_lock); std::lock_guard<std::mutex> lock(_lock);
@ -418,8 +465,9 @@ bool AsyncWebSocketClient::_queueMessage(AsyncWebSocketSharedBuffer buffer, uint
if (closeWhenFull) { if (closeWhenFull) {
_status = WS_DISCONNECTED; _status = WS_DISCONNECTED;
if (_client) if (_client) {
_client->close(true); _client->close(true);
}
#ifdef ESP8266 #ifdef ESP8266
ets_printf("AsyncWebSocketClient::_queueMessage: Too many messages queued: closing connection\n"); ets_printf("AsyncWebSocketClient::_queueMessage: Too many messages queued: closing connection\n");
@ -440,15 +488,17 @@ bool AsyncWebSocketClient::_queueMessage(AsyncWebSocketSharedBuffer buffer, uint
_messageQueue.emplace_back(buffer, opcode, mask); _messageQueue.emplace_back(buffer, opcode, mask);
if (_client && _client->canSend()) if (_client && _client->canSend()) {
_runQueue(); _runQueue();
}
return true; return true;
} }
void AsyncWebSocketClient::close(uint16_t code, const char *message) { void AsyncWebSocketClient::close(uint16_t code, const char *message) {
if (_status != WS_CONNECTED) if (_status != WS_CONNECTED) {
return; return;
}
_status = WS_DISCONNECTING; _status = WS_DISCONNECTING;
@ -456,8 +506,9 @@ void AsyncWebSocketClient::close(uint16_t code, const char* message) {
uint8_t packetLen = 2; uint8_t packetLen = 2;
if (message != NULL) { if (message != NULL) {
size_t mlen = strlen(message); size_t mlen = strlen(message);
if (mlen > 123) if (mlen > 123) {
mlen = 123; mlen = 123;
}
packetLen += mlen; packetLen += mlen;
} }
char *buf = (char *)malloc(packetLen); char *buf = (char *)malloc(packetLen);
@ -470,6 +521,11 @@ void AsyncWebSocketClient::close(uint16_t code, const char* message) {
_queueControl(WS_DISCONNECT, (uint8_t *)buf, packetLen); _queueControl(WS_DISCONNECT, (uint8_t *)buf, packetLen);
free(buf); free(buf);
return; return;
} else {
#ifdef ESP32
log_e("Failed to allocate");
_client->abort();
#endif
} }
} }
_queueControl(WS_DISCONNECT); _queueControl(WS_DISCONNECT);
@ -484,8 +540,9 @@ void AsyncWebSocketClient::_onError(int8_t) {
} }
void AsyncWebSocketClient::_onTimeout(uint32_t time) { void AsyncWebSocketClient::_onTimeout(uint32_t time) {
if (!_client) if (!_client) {
return; return;
}
// Serial.println("onTime"); // Serial.println("onTime");
(void)time; (void)time;
_client->close(true); _client->close(true);
@ -522,12 +579,14 @@ void AsyncWebSocketClient::_onData(void* pbuf, size_t plen) {
plen -= 2; plen -= 2;
} else if (_pinfo.len == 127 && plen >= 8) { } else if (_pinfo.len == 127 && plen >= 8) {
_pinfo.len = fdata[9] | (uint16_t)(fdata[8]) << 8 | (uint32_t)(fdata[7]) << 16 | (uint32_t)(fdata[6]) << 24 | (uint64_t)(fdata[5]) << 32 | (uint64_t)(fdata[4]) << 40 | (uint64_t)(fdata[3]) << 48 | (uint64_t)(fdata[2]) << 56; _pinfo.len = fdata[9] | (uint16_t)(fdata[8]) << 8 | (uint32_t)(fdata[7]) << 16 | (uint32_t)(fdata[6]) << 24 | (uint64_t)(fdata[5]) << 32
| (uint64_t)(fdata[4]) << 40 | (uint64_t)(fdata[3]) << 48 | (uint64_t)(fdata[2]) << 56;
data += 8; data += 8;
plen -= 8; plen -= 8;
} }
if (_pinfo.masked && plen >= 4) { // if ws.close() is called, Safari sends a close frame with plen 2 and masked bit set. We must not decrement plen which is already 0. if (_pinfo.masked
&& plen >= 4) { // if ws.close() is called, Safari sends a close frame with plen 2 and masked bit set. We must not decrement plen which is already 0.
memcpy(_pinfo.mask, data, 4); memcpy(_pinfo.mask, data, 4);
data += 4; data += 4;
plen -= 4; plen -= 4;
@ -538,9 +597,10 @@ void AsyncWebSocketClient::_onData(void* pbuf, size_t plen) {
const auto datalast = data[datalen]; const auto datalast = data[datalen];
if (_pinfo.masked) { if (_pinfo.masked) {
for (size_t i = 0; i < datalen; i++) for (size_t i = 0; i < datalen; i++) {
data[i] ^= _pinfo.mask[(_pinfo.index + i) % 4]; data[i] ^= _pinfo.mask[(_pinfo.index + i) % 4];
} }
}
if ((datalen + _pinfo.index) < _pinfo.len) { if ((datalen + _pinfo.index) < _pinfo.len) {
_pstate = 1; _pstate = 1;
@ -551,8 +611,9 @@ void AsyncWebSocketClient::_onData(void* pbuf, size_t plen) {
_pinfo.num = 0; _pinfo.num = 0;
} }
} }
if (datalen > 0) if (datalen > 0) {
_server->_handleEvent(this, WS_EVT_DATA, (void *)&_pinfo, data, datalen); _server->_handleEvent(this, WS_EVT_DATA, (void *)&_pinfo, data, datalen);
}
_pinfo.index += datalen; _pinfo.index += datalen;
} else if ((datalen + _pinfo.index) == _pinfo.len) { } else if ((datalen + _pinfo.index) == _pinfo.len) {
@ -567,27 +628,31 @@ void AsyncWebSocketClient::_onData(void* pbuf, size_t plen) {
} }
if (_status == WS_DISCONNECTING) { if (_status == WS_DISCONNECTING) {
_status = WS_DISCONNECTED; _status = WS_DISCONNECTED;
if (_client) if (_client) {
_client->close(true); _client->close(true);
}
} else { } else {
_status = WS_DISCONNECTING; _status = WS_DISCONNECTING;
if (_client) if (_client) {
_client->ackLater(); _client->ackLater();
}
_queueControl(WS_DISCONNECT, data, datalen); _queueControl(WS_DISCONNECT, data, datalen);
} }
} else if (_pinfo.opcode == WS_PING) { } else if (_pinfo.opcode == WS_PING) {
_server->_handleEvent(this, WS_EVT_PING, NULL, NULL, 0); _server->_handleEvent(this, WS_EVT_PING, NULL, NULL, 0);
_queueControl(WS_PONG, data, datalen); _queueControl(WS_PONG, data, datalen);
} else if (_pinfo.opcode == WS_PONG) { } else if (_pinfo.opcode == WS_PONG) {
if (datalen != AWSC_PING_PAYLOAD_LEN || memcmp(AWSC_PING_PAYLOAD, data, AWSC_PING_PAYLOAD_LEN) != 0) if (datalen != AWSC_PING_PAYLOAD_LEN || memcmp(AWSC_PING_PAYLOAD, data, AWSC_PING_PAYLOAD_LEN) != 0) {
_server->_handleEvent(this, WS_EVT_PONG, NULL, NULL, 0); _server->_handleEvent(this, WS_EVT_PONG, NULL, NULL, 0);
}
} else if (_pinfo.opcode < WS_DISCONNECT) { // continuation or text/binary frame } else if (_pinfo.opcode < WS_DISCONNECT) { // continuation or text/binary frame
_server->_handleEvent(this, WS_EVT_DATA, (void *)&_pinfo, data, datalen); _server->_handleEvent(this, WS_EVT_DATA, (void *)&_pinfo, data, datalen);
if (_pinfo.final) if (_pinfo.final) {
_pinfo.num = 0; _pinfo.num = 0;
else } else {
_pinfo.num += 1; _pinfo.num += 1;
} }
}
} else { } else {
// os_printf("frame error: len: %u, index: %llu, total: %llu\n", datalen, _pinfo.index, _pinfo.len); // os_printf("frame error: len: %u, index: %llu, total: %llu\n", datalen, _pinfo.index, _pinfo.len);
// what should we do? // what should we do?
@ -595,8 +660,9 @@ void AsyncWebSocketClient::_onData(void* pbuf, size_t plen) {
} }
// restore byte as _handleEvent may have added a null terminator i.e., data[len] = 0; // restore byte as _handleEvent may have added a null terminator i.e., data[len] = 0;
if (datalen) if (datalen) {
data[datalen] = datalast; data[datalen] = datalast;
}
data += datalen; data += datalen;
plen -= datalen; plen -= datalen;
@ -609,13 +675,15 @@ size_t AsyncWebSocketClient::printf(const char* format, ...) {
size_t len = vsnprintf(nullptr, 0, format, arg); size_t len = vsnprintf(nullptr, 0, format, arg);
va_end(arg); va_end(arg);
if (len == 0) if (len == 0) {
return 0; return 0;
}
char *buffer = new char[len + 1]; char *buffer = new char[len + 1];
if (!buffer) if (!buffer) {
return 0; return 0;
}
va_start(arg, format); va_start(arg, format);
len = vsnprintf(buffer, len + 1, format, arg); len = vsnprintf(buffer, len + 1, format, arg);
@ -633,13 +701,15 @@ size_t AsyncWebSocketClient::printf_P(PGM_P formatP, ...) {
size_t len = vsnprintf_P(nullptr, 0, formatP, arg); size_t len = vsnprintf_P(nullptr, 0, formatP, arg);
va_end(arg); va_end(arg);
if (len == 0) if (len == 0) {
return 0; return 0;
}
char *buffer = new char[len + 1]; char *buffer = new char[len + 1];
if (!buffer) if (!buffer) {
return 0; return 0;
}
va_start(arg, formatP); va_start(arg, formatP);
len = vsnprintf_P(buffer, len + 1, formatP, arg); len = vsnprintf_P(buffer, len + 1, formatP, arg);
@ -657,7 +727,7 @@ namespace {
std::memcpy(buffer->data(), message, len); std::memcpy(buffer->data(), message, len);
return buffer; return buffer;
} }
} } // namespace
bool AsyncWebSocketClient::text(AsyncWebSocketMessageBuffer *buffer) { bool AsyncWebSocketClient::text(AsyncWebSocketMessageBuffer *buffer) {
bool enqueued = false; bool enqueued = false;
@ -694,8 +764,9 @@ bool AsyncWebSocketClient::text(const __FlashStringHelper* data) {
size_t n = 0; size_t n = 0;
while (1) { while (1) {
if (pgm_read_byte(p + n) == 0) if (pgm_read_byte(p + n) == 0) {
break; break;
}
n += 1; n += 1;
} }
@ -755,15 +826,17 @@ bool AsyncWebSocketClient::binary(const __FlashStringHelper* data, size_t len) {
#endif #endif
IPAddress AsyncWebSocketClient::remoteIP() const { IPAddress AsyncWebSocketClient::remoteIP() const {
if (!_client) if (!_client) {
return IPAddress((uint32_t)0U); return IPAddress((uint32_t)0U);
}
return _client->remoteIP(); return _client->remoteIP();
} }
uint16_t AsyncWebSocketClient::remotePort() const { uint16_t AsyncWebSocketClient::remotePort() const {
if (!_client) if (!_client) {
return 0; return 0;
}
return _client->remotePort(); return _client->remotePort();
} }
@ -785,48 +858,62 @@ AsyncWebSocketClient* AsyncWebSocket::_newClient(AsyncWebServerRequest* request)
} }
bool AsyncWebSocket::availableForWriteAll() { bool AsyncWebSocket::availableForWriteAll() {
return std::none_of(std::begin(_clients), std::end(_clients), [](const AsyncWebSocketClient& c) { return c.queueIsFull(); }); return std::none_of(std::begin(_clients), std::end(_clients), [](const AsyncWebSocketClient &c) {
return c.queueIsFull();
});
} }
bool AsyncWebSocket::availableForWrite(uint32_t id) { bool AsyncWebSocket::availableForWrite(uint32_t id) {
const auto iter = std::find_if(std::begin(_clients), std::end(_clients), [id](const AsyncWebSocketClient& c) { return c.id() == id; }); const auto iter = std::find_if(std::begin(_clients), std::end(_clients), [id](const AsyncWebSocketClient &c) {
if (iter == std::end(_clients)) return c.id() == id;
});
if (iter == std::end(_clients)) {
return true; return true;
}
return !iter->queueIsFull(); return !iter->queueIsFull();
} }
size_t AsyncWebSocket::count() const { size_t AsyncWebSocket::count() const {
return std::count_if(std::begin(_clients), std::end(_clients), [](const AsyncWebSocketClient& c) { return c.status() == WS_CONNECTED; }); return std::count_if(std::begin(_clients), std::end(_clients), [](const AsyncWebSocketClient &c) {
return c.status() == WS_CONNECTED;
});
} }
AsyncWebSocketClient *AsyncWebSocket::client(uint32_t id) { AsyncWebSocketClient *AsyncWebSocket::client(uint32_t id) {
const auto iter = std::find_if(_clients.begin(), _clients.end(), [id](const AsyncWebSocketClient& c) { return c.id() == id && c.status() == WS_CONNECTED; }); const auto iter = std::find_if(_clients.begin(), _clients.end(), [id](const AsyncWebSocketClient &c) {
if (iter == std::end(_clients)) return c.id() == id && c.status() == WS_CONNECTED;
});
if (iter == std::end(_clients)) {
return nullptr; return nullptr;
}
return &(*iter); return &(*iter);
} }
void AsyncWebSocket::close(uint32_t id, uint16_t code, const char *message) { void AsyncWebSocket::close(uint32_t id, uint16_t code, const char *message) {
if (AsyncWebSocketClient* c = client(id)) if (AsyncWebSocketClient *c = client(id)) {
c->close(code, message); c->close(code, message);
} }
}
void AsyncWebSocket::closeAll(uint16_t code, const char *message) { void AsyncWebSocket::closeAll(uint16_t code, const char *message) {
for (auto& c : _clients) for (auto &c : _clients) {
if (c.status() == WS_CONNECTED) if (c.status() == WS_CONNECTED) {
c.close(code, message); c.close(code, message);
} }
}
}
void AsyncWebSocket::cleanupClients(uint16_t maxClients) { void AsyncWebSocket::cleanupClients(uint16_t maxClients) {
if (count() > maxClients) if (count() > maxClients) {
_clients.front().close(); _clients.front().close();
}
for (auto iter = std::begin(_clients); iter != std::end(_clients);) { for (auto i = _clients.begin(); i != _clients.end(); ++i) {
if (iter->shouldBeDeleted()) if (i->shouldBeDeleted()) {
iter = _clients.erase(iter); _clients.erase(i);
else break;
iter++; }
} }
} }
@ -838,11 +925,13 @@ bool AsyncWebSocket::ping(uint32_t id, const uint8_t* data, size_t len) {
AsyncWebSocket::SendStatus AsyncWebSocket::pingAll(const uint8_t *data, size_t len) { AsyncWebSocket::SendStatus AsyncWebSocket::pingAll(const uint8_t *data, size_t len) {
size_t hit = 0; size_t hit = 0;
size_t miss = 0; size_t miss = 0;
for (auto& c : _clients) for (auto &c : _clients) {
if (c.status() == WS_CONNECTED && c.ping(data, len)) if (c.status() == WS_CONNECTED && c.ping(data, len)) {
hit++; hit++;
else } else {
miss++; miss++;
}
}
return hit == 0 ? DISCARDED : (miss == 0 ? ENQUEUED : PARTIALLY_ENQUEUED); return hit == 0 ? DISCARDED : (miss == 0 ? ENQUEUED : PARTIALLY_ENQUEUED);
} }
@ -866,8 +955,9 @@ bool AsyncWebSocket::text(uint32_t id, const __FlashStringHelper* data) {
size_t n = 0; size_t n = 0;
while (true) { while (true) {
if (pgm_read_byte(p + n) == 0) if (pgm_read_byte(p + n) == 0) {
break; break;
}
n += 1; n += 1;
} }
@ -914,8 +1004,9 @@ AsyncWebSocket::SendStatus AsyncWebSocket::textAll(const __FlashStringHelper* da
size_t n = 0; size_t n = 0;
while (1) { while (1) {
if (pgm_read_byte(p + n) == 0) if (pgm_read_byte(p + n) == 0) {
break; break;
}
n += 1; n += 1;
} }
@ -942,11 +1033,13 @@ AsyncWebSocket::SendStatus AsyncWebSocket::textAll(AsyncWebSocketMessageBuffer*
AsyncWebSocket::SendStatus AsyncWebSocket::textAll(AsyncWebSocketSharedBuffer buffer) { AsyncWebSocket::SendStatus AsyncWebSocket::textAll(AsyncWebSocketSharedBuffer buffer) {
size_t hit = 0; size_t hit = 0;
size_t miss = 0; size_t miss = 0;
for (auto& c : _clients) for (auto &c : _clients) {
if (c.status() == WS_CONNECTED && c.text(buffer)) if (c.status() == WS_CONNECTED && c.text(buffer)) {
hit++; hit++;
else } else {
miss++; miss++;
}
}
return hit == 0 ? DISCARDED : (miss == 0 ? ENQUEUED : PARTIALLY_ENQUEUED); return hit == 0 ? DISCARDED : (miss == 0 ? ENQUEUED : PARTIALLY_ENQUEUED);
} }
@ -1029,11 +1122,13 @@ AsyncWebSocket::SendStatus AsyncWebSocket::binaryAll(AsyncWebSocketMessageBuffer
AsyncWebSocket::SendStatus AsyncWebSocket::binaryAll(AsyncWebSocketSharedBuffer buffer) { AsyncWebSocket::SendStatus AsyncWebSocket::binaryAll(AsyncWebSocketSharedBuffer buffer) {
size_t hit = 0; size_t hit = 0;
size_t miss = 0; size_t miss = 0;
for (auto& c : _clients) for (auto &c : _clients) {
if (c.status() == WS_CONNECTED && c.binary(buffer)) if (c.status() == WS_CONNECTED && c.binary(buffer)) {
hit++; hit++;
else } else {
miss++; miss++;
}
}
return hit == 0 ? DISCARDED : (miss == 0 ? ENQUEUED : PARTIALLY_ENQUEUED); return hit == 0 ? DISCARDED : (miss == 0 ? ENQUEUED : PARTIALLY_ENQUEUED);
} }
@ -1055,13 +1150,15 @@ size_t AsyncWebSocket::printfAll(const char* format, ...) {
size_t len = vsnprintf(nullptr, 0, format, arg); size_t len = vsnprintf(nullptr, 0, format, arg);
va_end(arg); va_end(arg);
if (len == 0) if (len == 0) {
return 0; return 0;
}
char *buffer = new char[len + 1]; char *buffer = new char[len + 1];
if (!buffer) if (!buffer) {
return 0; return 0;
}
va_start(arg, format); va_start(arg, format);
len = vsnprintf(buffer, len + 1, format, arg); len = vsnprintf(buffer, len + 1, format, arg);
@ -1091,13 +1188,15 @@ size_t AsyncWebSocket::printfAll_P(PGM_P formatP, ...) {
size_t len = vsnprintf_P(nullptr, 0, formatP, arg); size_t len = vsnprintf_P(nullptr, 0, formatP, arg);
va_end(arg); va_end(arg);
if (len == 0) if (len == 0) {
return 0; return 0;
}
char *buffer = new char[len + 1]; char *buffer = new char[len + 1];
if (!buffer) if (!buffer) {
return 0; return 0;
}
va_start(arg, formatP); va_start(arg, formatP);
len = vsnprintf_P(buffer, len + 1, formatP, arg); len = vsnprintf_P(buffer, len + 1, formatP, arg);
@ -1155,6 +1254,13 @@ void AsyncWebSocket::handleRequest(AsyncWebServerRequest* request) {
} }
const AsyncWebHeader *key = request->getHeader(WS_STR_KEY); const AsyncWebHeader *key = request->getHeader(WS_STR_KEY);
AsyncWebServerResponse *response = new AsyncWebSocketResponse(key->value(), this); AsyncWebServerResponse *response = new AsyncWebSocketResponse(key->value(), this);
if (response == NULL) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
request->abort();
return;
}
if (request->hasHeader(WS_STR_PROTOCOL)) { if (request->hasHeader(WS_STR_PROTOCOL)) {
const AsyncWebHeader *protocol = request->getHeader(WS_STR_PROTOCOL); const AsyncWebHeader *protocol = request->getHeader(WS_STR_PROTOCOL);
// ToDo: check protocol // ToDo: check protocol
@ -1164,23 +1270,11 @@ void AsyncWebSocket::handleRequest(AsyncWebServerRequest* request) {
} }
AsyncWebSocketMessageBuffer *AsyncWebSocket::makeBuffer(size_t size) { AsyncWebSocketMessageBuffer *AsyncWebSocket::makeBuffer(size_t size) {
AsyncWebSocketMessageBuffer* buffer = new AsyncWebSocketMessageBuffer(size); return new AsyncWebSocketMessageBuffer(size);
if (buffer->length() != size) {
delete buffer;
return nullptr;
} else {
return buffer;
}
} }
AsyncWebSocketMessageBuffer *AsyncWebSocket::makeBuffer(const uint8_t *data, size_t size) { AsyncWebSocketMessageBuffer *AsyncWebSocket::makeBuffer(const uint8_t *data, size_t size) {
AsyncWebSocketMessageBuffer* buffer = new AsyncWebSocketMessageBuffer(data, size); return new AsyncWebSocketMessageBuffer(data, size);
if (buffer->length() != size) {
delete buffer;
return nullptr;
} else {
return buffer;
}
} }
/* /*
@ -1196,11 +1290,14 @@ AsyncWebSocketResponse::AsyncWebSocketResponse(const String& key, AsyncWebSocket
uint8_t hash[20]; uint8_t hash[20];
char buffer[33]; char buffer[33];
#if defined(ESP8266) || defined(TARGET_RP2040) #if defined(ESP8266) || defined(TARGET_RP2040) || defined(PICO_RP2040) || defined(PICO_RP2350) || defined(TARGET_RP2350)
sha1(key + WS_STR_UUID, hash); sha1(key + WS_STR_UUID, hash);
#else #else
String k; String k;
k.reserve(key.length() + WS_STR_UUID_LEN); if (!k.reserve(key.length() + WS_STR_UUID_LEN)) {
log_e("Failed to allocate");
return;
}
k.concat(key); k.concat(key);
k.concat(WS_STR_UUID); k.concat(WS_STR_UUID);
SHA1Builder sha1; SHA1Builder sha1;
@ -1232,8 +1329,9 @@ void AsyncWebSocketResponse::_respond(AsyncWebServerRequest* request) {
size_t AsyncWebSocketResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time) { size_t AsyncWebSocketResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time) {
(void)time; (void)time;
if (len) if (len) {
_server->_newClient(request); _server->_newClient(request);
}
return 0; return 0;
} }

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
*/
#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
@ -33,8 +16,8 @@
#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
@ -90,24 +73,32 @@ typedef struct {
uint64_t index; uint64_t index;
} AwsFrameInfo; } AwsFrameInfo;
typedef enum { WS_DISCONNECTED, typedef enum {
WS_DISCONNECTED,
WS_CONNECTED, WS_CONNECTED,
WS_DISCONNECTING } AwsClientStatus; WS_DISCONNECTING
typedef enum { WS_CONTINUATION, } AwsClientStatus;
typedef enum {
WS_CONTINUATION,
WS_TEXT, WS_TEXT,
WS_BINARY, WS_BINARY,
WS_DISCONNECT = 0x08, WS_DISCONNECT = 0x08,
WS_PING, WS_PING,
WS_PONG } AwsFrameType; WS_PONG
typedef enum { WS_MSG_SENDING, } AwsFrameType;
typedef enum {
WS_MSG_SENDING,
WS_MSG_SENT, WS_MSG_SENT,
WS_MSG_ERROR } AwsMessageStatus; WS_MSG_ERROR
typedef enum { WS_EVT_CONNECT, } AwsMessageStatus;
typedef enum {
WS_EVT_CONNECT,
WS_EVT_DISCONNECT, WS_EVT_DISCONNECT,
WS_EVT_PING, WS_EVT_PING,
WS_EVT_PONG, WS_EVT_PONG,
WS_EVT_ERROR, WS_EVT_ERROR,
WS_EVT_DATA } AwsEventType; WS_EVT_DATA
} AwsEventType;
class AsyncWebSocketMessageBuffer { class AsyncWebSocketMessageBuffer {
friend AsyncWebSocket; friend AsyncWebSocket;
@ -122,8 +113,12 @@ class AsyncWebSocketMessageBuffer {
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 {
@ -139,8 +134,12 @@ class AsyncWebSocketMessage {
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);
@ -177,13 +176,27 @@ class AsyncWebSocketClient {
~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.
@ -203,13 +216,19 @@ class AsyncWebSocketClient {
// 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);
@ -224,7 +243,9 @@ class AsyncWebSocketClient {
} }
// 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) {
_queueMessage(buffer, opcode, mask);
}
bool queueIsFull() const; bool queueIsFull() const;
size_t queueLen() const; size_t queueLen() const;
@ -287,15 +308,23 @@ class AsyncWebSocket : public AsyncWebHandler {
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; } }
void enable(bool e) {
_enabled = e;
}
bool enabled() const {
return _enabled;
}
bool availableForWriteAll(); bool availableForWriteAll();
bool availableForWrite(uint32_t id); 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);
@ -344,11 +373,17 @@ class AsyncWebSocket : public AsyncWebHandler {
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() {
return _cNextId++;
}
AsyncWebSocketClient *_newClient(AsyncWebServerRequest *request); AsyncWebSocketClient *_newClient(AsyncWebServerRequest *request);
void _handleEvent(AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len); void _handleEvent(AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len);
bool canHandle(AsyncWebServerRequest *request) const override final; bool canHandle(AsyncWebServerRequest *request) const override final;
@ -358,7 +393,9 @@ class AsyncWebSocket : public AsyncWebHandler {
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
@ -371,7 +408,9 @@ class AsyncWebSocketResponse : public AsyncWebServerResponse {
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

@ -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,3 +1,6 @@
// 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
@ -13,6 +16,8 @@ class ChunkPrint : public Print {
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,23 +1,6 @@
/* // 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 _ESPAsyncWebServer_H_ #ifndef _ESPAsyncWebServer_H_
#define _ESPAsyncWebServer_H_ #define _ESPAsyncWebServer_H_
@ -32,13 +15,13 @@
#include <vector> #include <vector>
#ifdef ESP32 #ifdef ESP32
#include "../../ESP32Async-AsyncTCP/src/AsyncTCP.h" #include "../../mathieucarbou-AsyncTCPSock/src/AsyncTCP.h"
#include <WiFi.h> #include <WiFi.h>
#elif defined(ESP8266) #elif defined(ESP8266)
#include <ESP8266WiFi.h> #include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h> #include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) #elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <AsyncTCP_RP2040W.h> #include <RPAsyncTCP.h>
#include <HTTP_Method.h> #include <HTTP_Method.h>
#include <WiFi.h> #include <WiFi.h>
#include <http_parser.h> #include <http_parser.h>
@ -48,10 +31,7 @@
#include "literals.h" #include "literals.h"
#define ASYNCWEBSERVER_VERSION "3.6.2" #include "AsyncWebServerVersion.h"
#define ASYNCWEBSERVER_VERSION_MAJOR 3
#define ASYNCWEBSERVER_VERSION_MINOR 6
#define ASYNCWEBSERVER_VERSION_REVISION 2
#define ASYNCWEBSERVER_FORK_ESP32Async #define ASYNCWEBSERVER_FORK_ESP32Async
#ifdef ASYNCWEBSERVER_REGEX #ifdef ASYNCWEBSERVER_REGEX
@ -97,7 +77,7 @@ namespace fs {
static const char *write; static const char *write;
static const char *append; static const char *append;
}; };
}; }; // namespace fs
#else #else
#include "FileOpenMode.h" #include "FileOpenMode.h"
#endif #endif
@ -122,12 +102,23 @@ class AsyncWebParameter {
bool _isFile; bool _isFile;
public: public:
AsyncWebParameter(const String& name, const String& value, bool form = false, bool file = false, size_t size = 0) : _name(name), _value(value), _size(size), _isForm(form), _isFile(file) {} AsyncWebParameter(const String &name, const String &value, bool form = false, bool file = false, size_t size = 0)
const String& name() const { return _name; } : _name(name), _value(value), _size(size), _isForm(form), _isFile(file) {}
const String& value() const { return _value; } const String &name() const {
size_t size() const { return _size; } return _name;
bool isPost() const { return _isForm; } }
bool isFile() const { return _isFile; } const String &value() const {
return _value;
}
size_t size() const {
return _size;
}
bool isPost() const {
return _isForm;
}
bool isFile() const {
return _isFile;
}
}; };
/* /*
@ -147,8 +138,12 @@ class AsyncWebHeader {
AsyncWebHeader &operator=(const AsyncWebHeader &) = default; AsyncWebHeader &operator=(const AsyncWebHeader &) = default;
const String& name() const { return _name; } const String &name() const {
const String& value() const { return _value; } return _name;
}
const String &value() const {
return _value;
}
String toString() const; String toString() const;
}; };
@ -156,12 +151,14 @@ class AsyncWebHeader {
* REQUEST :: Each incoming Client is wrapped inside a Request and both live together until disconnect * REQUEST :: Each incoming Client is wrapped inside a Request and both live together until disconnect
* */ * */
typedef enum { RCT_NOT_USED = -1, typedef enum {
RCT_NOT_USED = -1,
RCT_DEFAULT = 0, RCT_DEFAULT = 0,
RCT_HTTP, RCT_HTTP,
RCT_WS, RCT_WS,
RCT_EVENT, RCT_EVENT,
RCT_MAX } RequestedConnectionType; RCT_MAX
} RequestedConnectionType;
// this enum is similar to Arduino WebServer's AsyncAuthType and PsychicHttp // this enum is similar to Arduino WebServer's AsyncAuthType and PsychicHttp
typedef enum { typedef enum {
@ -176,6 +173,8 @@ typedef enum {
typedef std::function<size_t(uint8_t *, size_t, size_t)> AwsResponseFiller; typedef std::function<size_t(uint8_t *, size_t, size_t)> AwsResponseFiller;
typedef std::function<String(const String &)> AwsTemplateProcessor; typedef std::function<String(const String &)> AwsTemplateProcessor;
using AsyncWebServerRequestPtr = std::weak_ptr<AsyncWebServerRequest>;
class AsyncWebServerRequest { class AsyncWebServerRequest {
using File = fs::File; using File = fs::File;
using FS = fs::FS; using FS = fs::FS;
@ -189,8 +188,9 @@ class AsyncWebServerRequest {
AsyncWebServerResponse *_response; AsyncWebServerResponse *_response;
ArDisconnectHandler _onDisconnectfn; ArDisconnectHandler _onDisconnectfn;
// response is sent bool _sent = false; // response is sent
bool _sent = false; bool _paused = false; // request is paused (request continuation)
std::shared_ptr<AsyncWebServerRequest> _this; // shared pointer to this request
String _temp; String _temp;
uint8_t _parseState; uint8_t _parseState;
@ -212,7 +212,7 @@ class AsyncWebServerRequest {
std::list<AsyncWebHeader> _headers; std::list<AsyncWebHeader> _headers;
std::list<AsyncWebParameter> _params; std::list<AsyncWebParameter> _params;
std::vector<String> _pathParams; std::list<String> _pathParams;
std::unordered_map<const char *, String, std::hash<const char *>, std::equal_to<const char *>> _attributes; std::unordered_map<const char *, String, std::hash<const char *>, std::equal_to<const char *>> _attributes;
@ -248,6 +248,9 @@ class AsyncWebServerRequest {
void _handleUploadByte(uint8_t data, bool last); void _handleUploadByte(uint8_t data, bool last);
void _handleUploadEnd(); void _handleUploadEnd();
void _send();
void _runMiddlewareChain();
public: public:
File _tempFile; File _tempFile;
void *_tempObject; void *_tempObject;
@ -255,23 +258,48 @@ class AsyncWebServerRequest {
AsyncWebServerRequest(AsyncWebServer *, AsyncClient *); AsyncWebServerRequest(AsyncWebServer *, AsyncClient *);
~AsyncWebServerRequest(); ~AsyncWebServerRequest();
AsyncClient* client() { return _client; } AsyncClient *client() {
uint8_t version() const { return _version; } return _client;
WebRequestMethodComposite method() const { return _method; } }
const String& url() const { return _url; } uint8_t version() const {
const String& host() const { return _host; } return _version;
const String& contentType() const { return _contentType; } }
size_t contentLength() const { return _contentLength; } WebRequestMethodComposite method() const {
bool multipart() const { return _isMultipart; } return _method;
}
const String &url() const {
return _url;
}
const String &host() const {
return _host;
}
const String &contentType() const {
return _contentType;
}
size_t contentLength() const {
return _contentLength;
}
bool multipart() const {
return _isMultipart;
}
const char *methodToString() const; const char *methodToString() const;
const char *requestedConnTypeToString() const; const char *requestedConnTypeToString() const;
RequestedConnectionType requestedConnType() const { return _reqconntype; } RequestedConnectionType requestedConnType() const {
bool isExpectedRequestedConnType(RequestedConnectionType erct1, RequestedConnectionType erct2 = RCT_NOT_USED, RequestedConnectionType erct3 = RCT_NOT_USED) const; return _reqconntype;
bool isWebSocketUpgrade() const { return _method == HTTP_GET && isExpectedRequestedConnType(RCT_WS); } }
bool isSSE() const { return _method == HTTP_GET && isExpectedRequestedConnType(RCT_EVENT); } bool isExpectedRequestedConnType(RequestedConnectionType erct1, RequestedConnectionType erct2 = RCT_NOT_USED, RequestedConnectionType erct3 = RCT_NOT_USED)
bool isHTTP() const { return isExpectedRequestedConnType(RCT_DEFAULT, RCT_HTTP); } const;
bool isWebSocketUpgrade() const {
return _method == HTTP_GET && isExpectedRequestedConnType(RCT_WS);
}
bool isSSE() const {
return _method == HTTP_GET && isExpectedRequestedConnType(RCT_EVENT);
}
bool isHTTP() const {
return isExpectedRequestedConnType(RCT_DEFAULT, RCT_HTTP);
}
void onDisconnect(ArDisconnectHandler fn); void onDisconnect(ArDisconnectHandler fn);
// hash is the string representation of: // hash is the string representation of:
@ -279,10 +307,17 @@ class AsyncWebServerRequest {
// user:realm:md5(user:realm:pass) for digest // user:realm:md5(user:realm:pass) for digest
bool authenticate(const char *hash) const; bool authenticate(const char *hash) const;
bool authenticate(const char *username, const char *credentials, const char *realm = NULL, bool isHash = false) const; bool authenticate(const char *username, const char *credentials, const char *realm = NULL, bool isHash = false) const;
void requestAuthentication(const char* realm = nullptr, bool isDigest = true) { requestAuthentication(isDigest ? AsyncAuthType::AUTH_DIGEST : AsyncAuthType::AUTH_BASIC, realm); } void requestAuthentication(const char *realm = nullptr, bool isDigest = true) {
requestAuthentication(isDigest ? AsyncAuthType::AUTH_DIGEST : AsyncAuthType::AUTH_BASIC, realm);
}
void requestAuthentication(AsyncAuthType method, const char *realm = nullptr, const char *_authFailMsg = nullptr); void requestAuthentication(AsyncAuthType method, const char *realm = nullptr, const char *_authFailMsg = nullptr);
void setHandler(AsyncWebHandler* handler) { _handler = handler; } // IMPORTANT: this method is for internal use ONLY
// Please do not use it!
// It can be removed or modified at any time without notice
void setHandler(AsyncWebHandler *handler) {
_handler = handler;
}
#ifndef ESP8266 #ifndef ESP8266
[[deprecated("All headers are now collected. Use removeHeader(name) or AsyncHeaderFreeMiddleware if you really need to free some headers.")]] [[deprecated("All headers are now collected. Use removeHeader(name) or AsyncHeaderFreeMiddleware if you really need to free some headers.")]]
@ -296,48 +331,80 @@ class AsyncWebServerRequest {
} }
/** /**
* @brief issue HTTP redirect responce with Location header * @brief issue HTTP redirect response with Location header
* *
* @param url - url to redirect to * @param url - url to redirect to
* @param code - responce code, default is 302 : temporary redirect * @param code - response code, default is 302 : temporary redirect
*/ */
void redirect(const char *url, int code = 302); void redirect(const char *url, int code = 302);
void redirect(const String& url, int code = 302) { return redirect(url.c_str(), code); }; void redirect(const String &url, int code = 302) {
return redirect(url.c_str(), code);
};
void send(AsyncWebServerResponse *response); void send(AsyncWebServerResponse *response);
AsyncWebServerResponse* getResponse() const { return _response; } AsyncWebServerResponse *getResponse() const {
return _response;
}
void send(int code, const char* contentType = asyncsrv::empty, const char* content = asyncsrv::empty, AwsTemplateProcessor callback = nullptr) { send(beginResponse(code, contentType, content, callback)); } void send(int code, const char *contentType = asyncsrv::empty, const char *content = asyncsrv::empty, AwsTemplateProcessor callback = nullptr) {
void send(int code, const String& contentType, const char* content = asyncsrv::empty, AwsTemplateProcessor callback = nullptr) { send(beginResponse(code, contentType.c_str(), content, callback)); } send(beginResponse(code, contentType, content, callback));
void send(int code, const String& contentType, const String& content, AwsTemplateProcessor callback = nullptr) { send(beginResponse(code, contentType.c_str(), content.c_str(), callback)); } }
void send(int code, const String &contentType, const char *content = asyncsrv::empty, AwsTemplateProcessor callback = nullptr) {
send(beginResponse(code, contentType.c_str(), content, callback));
}
void send(int code, const String &contentType, const String &content, AwsTemplateProcessor callback = nullptr) {
send(beginResponse(code, contentType.c_str(), content.c_str(), callback));
}
void send(int code, const char* contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr) { send(beginResponse(code, contentType, content, len, callback)); } void send(int code, const char *contentType, const uint8_t *content, size_t len, AwsTemplateProcessor callback = nullptr) {
void send(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr) { send(beginResponse(code, contentType, content, len, callback)); } send(beginResponse(code, contentType, content, len, callback));
}
void send(int code, const String &contentType, const uint8_t *content, size_t len, AwsTemplateProcessor callback = nullptr) {
send(beginResponse(code, contentType, content, len, callback));
}
void send(FS &fs, const String &path, const char *contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr) { void send(FS &fs, const String &path, const char *contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr) {
if (fs.exists(path) || (!download && fs.exists(path + asyncsrv::T__gz))) { if (fs.exists(path) || (!download && fs.exists(path + asyncsrv::T__gz))) {
send(beginResponse(fs, path, contentType, download, callback)); send(beginResponse(fs, path, contentType, download, callback));
} else } else {
send(404); send(404);
} }
void send(FS& fs, const String& path, const String& contentType, bool download = false, AwsTemplateProcessor callback = nullptr) { send(fs, path, contentType.c_str(), download, callback); } }
void send(FS &fs, const String &path, const String &contentType, bool download = false, AwsTemplateProcessor callback = nullptr) {
send(fs, path, contentType.c_str(), download, callback);
}
void send(File content, const String &path, const char *contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr) { void send(File content, const String &path, const char *contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr) {
if (content) { if (content) {
send(beginResponse(content, path, contentType, download, callback)); send(beginResponse(content, path, contentType, download, callback));
} else } else {
send(404); send(404);
} }
void send(File content, const String& path, const String& contentType, bool download = false, AwsTemplateProcessor callback = nullptr) { send(content, path, contentType.c_str(), download, callback); } }
void send(File content, const String &path, const String &contentType, bool download = false, AwsTemplateProcessor callback = nullptr) {
send(content, path, contentType.c_str(), download, callback);
}
void send(Stream& stream, const char* contentType, size_t len, AwsTemplateProcessor callback = nullptr) { send(beginResponse(stream, contentType, len, callback)); } void send(Stream &stream, const char *contentType, size_t len, AwsTemplateProcessor callback = nullptr) {
void send(Stream& stream, const String& contentType, size_t len, AwsTemplateProcessor callback = nullptr) { send(beginResponse(stream, contentType, len, callback)); } send(beginResponse(stream, contentType, len, callback));
}
void send(Stream &stream, const String &contentType, size_t len, AwsTemplateProcessor callback = nullptr) {
send(beginResponse(stream, contentType, len, callback));
}
void send(const char* contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) { send(beginResponse(contentType, len, callback, templateCallback)); } void send(const char *contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) {
void send(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) { send(beginResponse(contentType, len, callback, templateCallback)); } send(beginResponse(contentType, len, callback, templateCallback));
}
void send(const String &contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) {
send(beginResponse(contentType, len, callback, templateCallback));
}
void sendChunked(const char* contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) { send(beginChunkedResponse(contentType, callback, templateCallback)); } void sendChunked(const char *contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) {
void sendChunked(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) { send(beginChunkedResponse(contentType, callback, templateCallback)); } send(beginChunkedResponse(contentType, callback, templateCallback));
}
void sendChunked(const String &contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) {
send(beginChunkedResponse(contentType, callback, templateCallback));
}
#ifndef ESP8266 #ifndef ESP8266
[[deprecated("Replaced by send(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr)")]] [[deprecated("Replaced by send(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr)")]]
@ -356,30 +423,51 @@ class AsyncWebServerRequest {
} }
#endif #endif
AsyncWebServerResponse* beginResponse(int code, const char* contentType = asyncsrv::empty, const char* content = asyncsrv::empty, AwsTemplateProcessor callback = nullptr); AsyncWebServerResponse *
AsyncWebServerResponse* beginResponse(int code, const String& contentType, const char* content = asyncsrv::empty, AwsTemplateProcessor callback = nullptr) { return beginResponse(code, contentType.c_str(), content, callback); } beginResponse(int code, const char *contentType = asyncsrv::empty, const char *content = asyncsrv::empty, AwsTemplateProcessor callback = nullptr);
AsyncWebServerResponse* beginResponse(int code, const String& contentType, const String& content, AwsTemplateProcessor callback = nullptr) { return beginResponse(code, contentType.c_str(), content.c_str(), callback); } AsyncWebServerResponse *beginResponse(int code, const String &contentType, const char *content = asyncsrv::empty, AwsTemplateProcessor callback = nullptr) {
return beginResponse(code, contentType.c_str(), content, callback);
}
AsyncWebServerResponse *beginResponse(int code, const String &contentType, const String &content, AwsTemplateProcessor callback = nullptr) {
return beginResponse(code, contentType.c_str(), content.c_str(), callback);
}
AsyncWebServerResponse *beginResponse(int code, const char *contentType, const uint8_t *content, size_t len, AwsTemplateProcessor callback = nullptr); AsyncWebServerResponse *beginResponse(int code, const char *contentType, const uint8_t *content, size_t len, AwsTemplateProcessor callback = nullptr);
AsyncWebServerResponse* beginResponse(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr) { return beginResponse(code, contentType.c_str(), content, len, callback); } AsyncWebServerResponse *beginResponse(int code, const String &contentType, const uint8_t *content, size_t len, AwsTemplateProcessor callback = nullptr) {
return beginResponse(code, contentType.c_str(), content, len, callback);
}
AsyncWebServerResponse* beginResponse(FS& fs, const String& path, const char* contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr); AsyncWebServerResponse *
AsyncWebServerResponse* beginResponse(FS& fs, const String& path, const String& contentType = emptyString, bool download = false, AwsTemplateProcessor callback = nullptr) { return beginResponse(fs, path, contentType.c_str(), download, callback); } beginResponse(FS &fs, const String &path, const char *contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr);
AsyncWebServerResponse *
beginResponse(FS &fs, const String &path, const String &contentType = emptyString, bool download = false, AwsTemplateProcessor callback = nullptr) {
return beginResponse(fs, path, contentType.c_str(), download, callback);
}
AsyncWebServerResponse* beginResponse(File content, const String& path, const char* contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr); AsyncWebServerResponse *
AsyncWebServerResponse* beginResponse(File content, const String& path, const String& contentType = emptyString, bool download = false, AwsTemplateProcessor callback = nullptr) { return beginResponse(content, path, contentType.c_str(), download, callback); } beginResponse(File content, const String &path, const char *contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr);
AsyncWebServerResponse *
beginResponse(File content, const String &path, const String &contentType = emptyString, bool download = false, AwsTemplateProcessor callback = nullptr) {
return beginResponse(content, path, contentType.c_str(), download, callback);
}
AsyncWebServerResponse *beginResponse(Stream &stream, const char *contentType, size_t len, AwsTemplateProcessor callback = nullptr); AsyncWebServerResponse *beginResponse(Stream &stream, const char *contentType, size_t len, AwsTemplateProcessor callback = nullptr);
AsyncWebServerResponse* beginResponse(Stream& stream, const String& contentType, size_t len, AwsTemplateProcessor callback = nullptr) { return beginResponse(stream, contentType.c_str(), len, callback); } AsyncWebServerResponse *beginResponse(Stream &stream, const String &contentType, size_t len, AwsTemplateProcessor callback = nullptr) {
return beginResponse(stream, contentType.c_str(), len, callback);
}
AsyncWebServerResponse *beginResponse(const char *contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr); AsyncWebServerResponse *beginResponse(const char *contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr);
AsyncWebServerResponse* beginResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) { return beginResponse(contentType.c_str(), len, callback, templateCallback); } AsyncWebServerResponse *beginResponse(const String &contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) {
return beginResponse(contentType.c_str(), len, callback, templateCallback);
}
AsyncWebServerResponse *beginChunkedResponse(const char *contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr); AsyncWebServerResponse *beginChunkedResponse(const char *contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr);
AsyncWebServerResponse *beginChunkedResponse(const String &contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr); AsyncWebServerResponse *beginChunkedResponse(const String &contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr);
AsyncResponseStream *beginResponseStream(const char *contentType, size_t bufferSize = RESPONSE_STREAM_BUFFER_SIZE); AsyncResponseStream *beginResponseStream(const char *contentType, size_t bufferSize = RESPONSE_STREAM_BUFFER_SIZE);
AsyncResponseStream* beginResponseStream(const String& contentType, size_t bufferSize = RESPONSE_STREAM_BUFFER_SIZE) { return beginResponseStream(contentType.c_str(), bufferSize); } AsyncResponseStream *beginResponseStream(const String &contentType, size_t bufferSize = RESPONSE_STREAM_BUFFER_SIZE) {
return beginResponseStream(contentType.c_str(), bufferSize);
}
#ifndef ESP8266 #ifndef ESP8266
[[deprecated("Replaced by beginResponse(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr)")]] [[deprecated("Replaced by beginResponse(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr)")]]
@ -388,10 +476,37 @@ class AsyncWebServerRequest {
return beginResponse(code, contentType.c_str(), content, len, callback); return beginResponse(code, contentType.c_str(), content, len, callback);
} }
#ifndef ESP8266 #ifndef ESP8266
[[deprecated("Replaced by beginResponse(int code, const String& contentType, const char* content = asyncsrv::empty, AwsTemplateProcessor callback = nullptr)")]] [[deprecated("Replaced by beginResponse(int code, const String& contentType, const char* content = asyncsrv::empty, AwsTemplateProcessor callback = nullptr)"
)]]
#endif #endif
AsyncWebServerResponse *beginResponse_P(int code, const String &contentType, PGM_P content, AwsTemplateProcessor callback = nullptr); AsyncWebServerResponse *beginResponse_P(int code, const String &contentType, PGM_P content, AwsTemplateProcessor callback = nullptr);
/**
* @brief Request Continuation: this function pauses the current request and returns a weak pointer (AsyncWebServerRequestPtr is a std::weak_ptr) to the request in order to reuse it later on.
* The middelware chain will continue to be processed until the end, but no response will be sent.
* To resume operations (send the request), the request must be retrieved from the weak pointer and a send() function must be called.
* AsyncWebServerRequestPtr is the only object allowed to exist the scope of the request handler.
* @warning This function should be called from within the context of a request (in a handler or middleware for example).
* @warning While the request is paused, if the client aborts the request, the latter will be disconnected and deleted.
* So it is the responsibility of the user to check the validity of the request pointer (AsyncWebServerRequestPtr) before using it by calling lock() and/or expired().
*/
AsyncWebServerRequestPtr pause();
bool isPaused() const {
return _paused;
}
/**
* @brief Aborts the request and close the client (RST).
* Mark the request as sent.
* If it was paused, it will be unpaused and it won't be possible to resume it.
*/
void abort();
bool isSent() const {
return _sent;
}
/** /**
* @brief Get the Request parameter by name * @brief Get the Request parameter by name
* *
@ -402,7 +517,9 @@ class AsyncWebServerRequest {
*/ */
const AsyncWebParameter *getParam(const char *name, bool post = false, bool file = false) const; const AsyncWebParameter *getParam(const char *name, bool post = false, bool file = false) const;
const AsyncWebParameter* getParam(const String& name, bool post = false, bool file = false) const { return getParam(name.c_str(), post, file); }; const AsyncWebParameter *getParam(const String &name, bool post = false, bool file = false) const {
return getParam(name.c_str(), post, file);
};
#ifdef ESP8266 #ifdef ESP8266
const AsyncWebParameter *getParam(const __FlashStringHelper *data, bool post, bool file) const; const AsyncWebParameter *getParam(const __FlashStringHelper *data, bool post, bool file) const;
#endif #endif
@ -415,19 +532,25 @@ class AsyncWebServerRequest {
*/ */
const AsyncWebParameter *getParam(size_t num) const; const AsyncWebParameter *getParam(size_t num) const;
size_t args() const { return params(); } // get arguments count size_t args() const {
return params();
} // get arguments count
// get request argument value by name // get request argument value by name
const String &arg(const char *name) const; const String &arg(const char *name) const;
// get request argument value by name // get request argument value by name
const String& arg(const String& name) const { return arg(name.c_str()); }; const String &arg(const String &name) const {
return arg(name.c_str());
};
#ifdef ESP8266 #ifdef ESP8266
const String &arg(const __FlashStringHelper *data) const; // get request argument value by F(name) const String &arg(const __FlashStringHelper *data) const; // get request argument value by F(name)
#endif #endif
const String &arg(size_t i) const; // get request argument value by number const String &arg(size_t i) const; // get request argument value by number
const String &argName(size_t i) const; // get request argument name by number const String &argName(size_t i) const; // get request argument name by number
bool hasArg(const char *name) const; // check if argument exists bool hasArg(const char *name) const; // check if argument exists
bool hasArg(const String& name) const { return hasArg(name.c_str()); }; bool hasArg(const String &name) const {
return hasArg(name.c_str());
};
#ifdef ESP8266 #ifdef ESP8266
bool hasArg(const __FlashStringHelper *data) const; // check if F(argument) exists bool hasArg(const __FlashStringHelper *data) const; // check if F(argument) exists
#endif #endif
@ -436,7 +559,9 @@ class AsyncWebServerRequest {
// get request header value by name // get request header value by name
const String &header(const char *name) const; const String &header(const char *name) const;
const String& header(const String& name) const { return header(name.c_str()); }; const String &header(const String &name) const {
return header(name.c_str());
};
#ifdef ESP8266 #ifdef ESP8266
const String &header(const __FlashStringHelper *data) const; // get request header value by F(name) const String &header(const __FlashStringHelper *data) const; // get request header value by F(name)
@ -449,20 +574,26 @@ class AsyncWebServerRequest {
// check if header exists // check if header exists
bool hasHeader(const char *name) const; bool hasHeader(const char *name) const;
bool hasHeader(const String& name) const { return hasHeader(name.c_str()); }; bool hasHeader(const String &name) const {
return hasHeader(name.c_str());
};
#ifdef ESP8266 #ifdef ESP8266
bool hasHeader(const __FlashStringHelper *data) const; // check if header exists bool hasHeader(const __FlashStringHelper *data) const; // check if header exists
#endif #endif
const AsyncWebHeader *getHeader(const char *name) const; const AsyncWebHeader *getHeader(const char *name) const;
const AsyncWebHeader* getHeader(const String& name) const { return getHeader(name.c_str()); }; const AsyncWebHeader *getHeader(const String &name) const {
return getHeader(name.c_str());
};
#ifdef ESP8266 #ifdef ESP8266
const AsyncWebHeader *getHeader(const __FlashStringHelper *data) const; const AsyncWebHeader *getHeader(const __FlashStringHelper *data) const;
#endif #endif
const AsyncWebHeader *getHeader(size_t num) const; const AsyncWebHeader *getHeader(size_t num) const;
const std::list<AsyncWebHeader>& getHeaders() const { return _headers; } const std::list<AsyncWebHeader> &getHeaders() const {
return _headers;
}
size_t getHeaderNames(std::vector<const char *> &names) const; size_t getHeaderNames(std::vector<const char *> &names) const;
@ -470,24 +601,42 @@ class AsyncWebServerRequest {
// It will free the memory and prevent the header to be seen during request processing. // It will free the memory and prevent the header to be seen during request processing.
bool removeHeader(const char *name); bool removeHeader(const char *name);
// Remove all request headers. // Remove all request headers.
void removeHeaders() { _headers.clear(); } void removeHeaders() {
_headers.clear();
}
size_t params() const; // get arguments count size_t params() const; // get arguments count
bool hasParam(const char *name, bool post = false, bool file = false) const; bool hasParam(const char *name, bool post = false, bool file = false) const;
bool hasParam(const String& name, bool post = false, bool file = false) const { return hasParam(name.c_str(), post, file); }; bool hasParam(const String &name, bool post = false, bool file = false) const {
return hasParam(name.c_str(), post, file);
};
#ifdef ESP8266 #ifdef ESP8266
bool hasParam(const __FlashStringHelper* data, bool post = false, bool file = false) const { return hasParam(String(data).c_str(), post, file); }; bool hasParam(const __FlashStringHelper *data, bool post = false, bool file = false) const {
return hasParam(String(data).c_str(), post, file);
};
#endif #endif
// REQUEST ATTRIBUTES // REQUEST ATTRIBUTES
void setAttribute(const char* name, const char* value) { _attributes[name] = value; } void setAttribute(const char *name, const char *value) {
void setAttribute(const char* name, bool value) { _attributes[name] = value ? "1" : emptyString; } _attributes[name] = value;
void setAttribute(const char* name, long value) { _attributes[name] = String(value); } }
void setAttribute(const char* name, float value, unsigned int decimalPlaces = 2) { _attributes[name] = String(value, decimalPlaces); } void setAttribute(const char *name, bool value) {
void setAttribute(const char* name, double value, unsigned int decimalPlaces = 2) { _attributes[name] = String(value, decimalPlaces); } _attributes[name] = value ? "1" : emptyString;
}
void setAttribute(const char *name, long value) {
_attributes[name] = String(value);
}
void setAttribute(const char *name, float value, unsigned int decimalPlaces = 2) {
_attributes[name] = String(value, decimalPlaces);
}
void setAttribute(const char *name, double value, unsigned int decimalPlaces = 2) {
_attributes[name] = String(value, decimalPlaces);
}
bool hasAttribute(const char* name) const { return _attributes.find(name) != _attributes.end(); } bool hasAttribute(const char *name) const {
return _attributes.find(name) != _attributes.end();
}
const String &getAttribute(const char *name, const String &defaultValue = emptyString) const; const String &getAttribute(const char *name, const String &defaultValue = emptyString) const;
bool getAttribute(const char *name, bool defaultValue) const; bool getAttribute(const char *name, bool defaultValue) const;
@ -521,7 +670,9 @@ using ArMiddlewareCallback = std::function<void(AsyncWebServerRequest* request,
class AsyncMiddleware { class AsyncMiddleware {
public: public:
virtual ~AsyncMiddleware() {} virtual ~AsyncMiddleware() {}
virtual void run(__unused AsyncWebServerRequest* request, __unused ArMiddlewareNext next) { return next(); }; virtual void run(__unused AsyncWebServerRequest *request, __unused ArMiddlewareNext next) {
return next();
};
private: private:
friend class AsyncWebHandler; friend class AsyncWebHandler;
@ -534,7 +685,9 @@ class AsyncMiddleware {
class AsyncMiddlewareFunction : public AsyncMiddleware { class AsyncMiddlewareFunction : public AsyncMiddleware {
public: public:
AsyncMiddlewareFunction(ArMiddlewareCallback fn) : _fn(fn) {} AsyncMiddlewareFunction(ArMiddlewareCallback fn) : _fn(fn) {}
void run(AsyncWebServerRequest* request, ArMiddlewareNext next) override { return _fn(request, next); }; void run(AsyncWebServerRequest *request, ArMiddlewareNext next) override {
return _fn(request, next);
};
private: private:
ArMiddlewareCallback _fn; ArMiddlewareCallback _fn;
@ -564,8 +717,12 @@ class AsyncAuthenticationMiddleware : public AsyncMiddleware {
void setPassword(const char *password); void setPassword(const char *password);
void setPasswordHash(const char *hash); void setPasswordHash(const char *hash);
void setRealm(const char* realm) { _realm = realm; } void setRealm(const char *realm) {
void setAuthFailureMessage(const char* message) { _authFailMsg = message; } _realm = realm;
}
void setAuthFailureMessage(const char *message) {
_authFailMsg = message;
}
// set the authentication method to use // set the authentication method to use
// default is AUTH_NONE: no authentication required // default is AUTH_NONE: no authentication required
@ -575,7 +732,9 @@ class AsyncAuthenticationMiddleware : public AsyncMiddleware {
// AUTH_OTHER: other authentication method // AUTH_OTHER: other authentication method
// AUTH_DENIED: always return 401 Unauthorized // AUTH_DENIED: always return 401 Unauthorized
// if a method is set but no username or password is set, authentication will be ignored // if a method is set but no username or password is set, authentication will be ignored
void setAuthType(AsyncAuthType authMethod) { _authMethod = authMethod; } void setAuthType(AsyncAuthType authMethod) {
_authMethod = authMethod;
}
// precompute and store the hash value based on the username, password, realm. // precompute and store the hash value based on the username, password, realm.
// can be used for DIGEST and BASIC to avoid recomputing the hash for each request. // can be used for DIGEST and BASIC to avoid recomputing the hash for each request.
@ -583,7 +742,9 @@ class AsyncAuthenticationMiddleware : public AsyncMiddleware {
bool generateHash(); bool generateHash();
// returns true if the username and password (or hash) are set // returns true if the username and password (or hash) are set
bool hasCredentials() const { return _hasCreds; } bool hasCredentials() const {
return _hasCreds;
}
bool allowed(AsyncWebServerRequest *request) const; bool allowed(AsyncWebServerRequest *request) const;
@ -607,7 +768,9 @@ class AsyncAuthorizationMiddleware : public AsyncMiddleware {
AsyncAuthorizationMiddleware(ArAuthorizeFunction authorizeConnectHandler) : _code(403), _authz(authorizeConnectHandler) {} AsyncAuthorizationMiddleware(ArAuthorizeFunction authorizeConnectHandler) : _code(403), _authz(authorizeConnectHandler) {}
AsyncAuthorizationMiddleware(int code, ArAuthorizeFunction authorizeConnectHandler) : _code(code), _authz(authorizeConnectHandler) {} AsyncAuthorizationMiddleware(int code, ArAuthorizeFunction authorizeConnectHandler) : _code(code), _authz(authorizeConnectHandler) {}
void run(AsyncWebServerRequest* request, ArMiddlewareNext next) { return _authz && !_authz(request) ? request->send(_code) : next(); } void run(AsyncWebServerRequest *request, ArMiddlewareNext next) {
return _authz && !_authz(request) ? request->send(_code) : next();
}
private: private:
int _code; int _code;
@ -617,33 +780,47 @@ class AsyncAuthorizationMiddleware : public AsyncMiddleware {
// remove all headers from the incoming request except the ones provided in the constructor // remove all headers from the incoming request except the ones provided in the constructor
class AsyncHeaderFreeMiddleware : public AsyncMiddleware { class AsyncHeaderFreeMiddleware : public AsyncMiddleware {
public: public:
void keep(const char* name) { _toKeep.push_back(name); } void keep(const char *name) {
void unKeep(const char* name) { _toKeep.erase(std::remove(_toKeep.begin(), _toKeep.end(), name), _toKeep.end()); } _toKeep.push_back(name);
}
void unKeep(const char *name) {
_toKeep.remove(name);
}
void run(AsyncWebServerRequest *request, ArMiddlewareNext next); void run(AsyncWebServerRequest *request, ArMiddlewareNext next);
private: private:
std::vector<const char*> _toKeep; std::list<const char *> _toKeep;
}; };
// filter out specific headers from the incoming request // filter out specific headers from the incoming request
class AsyncHeaderFilterMiddleware : public AsyncMiddleware { class AsyncHeaderFilterMiddleware : public AsyncMiddleware {
public: public:
void filter(const char* name) { _toRemove.push_back(name); } void filter(const char *name) {
void unFilter(const char* name) { _toRemove.erase(std::remove(_toRemove.begin(), _toRemove.end(), name), _toRemove.end()); } _toRemove.push_back(name);
}
void unFilter(const char *name) {
_toRemove.remove(name);
}
void run(AsyncWebServerRequest *request, ArMiddlewareNext next); void run(AsyncWebServerRequest *request, ArMiddlewareNext next);
private: private:
std::vector<const char*> _toRemove; std::list<const char *> _toRemove;
}; };
// curl-like logging of incoming requests // curl-like logging of incoming requests
class AsyncLoggingMiddleware : public AsyncMiddleware { class AsyncLoggingMiddleware : public AsyncMiddleware {
public: public:
void setOutput(Print& output) { _out = &output; } void setOutput(Print &output) {
void setEnabled(bool enabled) { _enabled = enabled; } _out = &output;
bool isEnabled() const { return _enabled && _out; } }
void setEnabled(bool enabled) {
_enabled = enabled;
}
bool isEnabled() const {
return _enabled && _out;
}
void run(AsyncWebServerRequest *request, ArMiddlewareNext next); void run(AsyncWebServerRequest *request, ArMiddlewareNext next);
@ -655,11 +832,21 @@ class AsyncLoggingMiddleware : public AsyncMiddleware {
// CORS Middleware // CORS Middleware
class AsyncCorsMiddleware : public AsyncMiddleware { class AsyncCorsMiddleware : public AsyncMiddleware {
public: public:
void setOrigin(const char* origin) { _origin = origin; } void setOrigin(const char *origin) {
void setMethods(const char* methods) { _methods = methods; } _origin = origin;
void setHeaders(const char* headers) { _headers = headers; } }
void setAllowCredentials(bool credentials) { _credentials = credentials; } void setMethods(const char *methods) {
void setMaxAge(uint32_t seconds) { _maxAge = seconds; } _methods = methods;
}
void setHeaders(const char *headers) {
_headers = headers;
}
void setAllowCredentials(bool credentials) {
_credentials = credentials;
}
void setMaxAge(uint32_t seconds) {
_maxAge = seconds;
}
void addCORSHeaders(AsyncWebServerResponse *response); void addCORSHeaders(AsyncWebServerResponse *response);
@ -676,8 +863,12 @@ class AsyncCorsMiddleware : public AsyncMiddleware {
// Rate limit Middleware // Rate limit Middleware
class AsyncRateLimitMiddleware : public AsyncMiddleware { class AsyncRateLimitMiddleware : public AsyncMiddleware {
public: public:
void setMaxRequests(size_t maxRequests) { _maxRequests = maxRequests; } void setMaxRequests(size_t maxRequests) {
void setWindowSize(uint32_t seconds) { _windowSizeMillis = seconds * 1000; } _maxRequests = maxRequests;
}
void setWindowSize(uint32_t seconds) {
_windowSizeMillis = seconds * 1000;
}
bool isRequestAllowed(uint32_t &retryAfterSeconds); bool isRequestAllowed(uint32_t &retryAfterSeconds);
@ -713,11 +904,21 @@ class AsyncWebRewrite {
_filter = fn; _filter = fn;
return *this; return *this;
} }
bool filter(AsyncWebServerRequest* request) const { return _filter == NULL || _filter(request); } bool filter(AsyncWebServerRequest *request) const {
const String& from(void) const { return _from; } return _filter == NULL || _filter(request);
const String& toUrl(void) const { return _toUrl; } }
const String& params(void) const { return _params; } const String &from(void) const {
virtual bool match(AsyncWebServerRequest* request) { return from() == request->url() && filter(request); } return _from;
}
const String &toUrl(void) const {
return _toUrl;
}
const String &params(void) const {
return _params;
}
virtual bool match(AsyncWebServerRequest *request) {
return from() == request->url() && filter(request);
}
}; };
/* /*
@ -728,19 +929,42 @@ class AsyncWebHandler : public AsyncMiddlewareChain {
protected: protected:
ArRequestFilterFunction _filter = nullptr; ArRequestFilterFunction _filter = nullptr;
AsyncAuthenticationMiddleware *_authMiddleware = nullptr; AsyncAuthenticationMiddleware *_authMiddleware = nullptr;
bool _skipServerMiddlewares = false;
public: public:
AsyncWebHandler() {} AsyncWebHandler() {}
virtual ~AsyncWebHandler() {} virtual ~AsyncWebHandler() {}
AsyncWebHandler &setFilter(ArRequestFilterFunction fn); AsyncWebHandler &setFilter(ArRequestFilterFunction fn);
AsyncWebHandler &setAuthentication(const char *username, const char *password, AsyncAuthType authMethod = AsyncAuthType::AUTH_DIGEST); AsyncWebHandler &setAuthentication(const char *username, const char *password, AsyncAuthType authMethod = AsyncAuthType::AUTH_DIGEST);
AsyncWebHandler& setAuthentication(const String& username, const String& password, AsyncAuthType authMethod = AsyncAuthType::AUTH_DIGEST) { return setAuthentication(username.c_str(), password.c_str(), authMethod); }; AsyncWebHandler &setAuthentication(const String &username, const String &password, AsyncAuthType authMethod = AsyncAuthType::AUTH_DIGEST) {
bool filter(AsyncWebServerRequest* request) { return _filter == NULL || _filter(request); } return setAuthentication(username.c_str(), password.c_str(), authMethod);
virtual bool canHandle(AsyncWebServerRequest* request __attribute__((unused))) const { return false; } };
AsyncWebHandler &setSkipServerMiddlewares(bool state) {
_skipServerMiddlewares = state;
return *this;
}
// skip all globally defined server middlewares for this handler and only execute those defined for this handler specifically
AsyncWebHandler &skipServerMiddlewares() {
return setSkipServerMiddlewares(true);
}
bool mustSkipServerMiddlewares() const {
return _skipServerMiddlewares;
}
bool filter(AsyncWebServerRequest *request) {
return _filter == NULL || _filter(request);
}
virtual bool canHandle(AsyncWebServerRequest *request __attribute__((unused))) const {
return false;
}
virtual void handleRequest(__unused AsyncWebServerRequest *request) {} virtual void handleRequest(__unused AsyncWebServerRequest *request) {}
virtual void handleUpload(__unused AsyncWebServerRequest* request, __unused const String& filename, __unused size_t index, __unused uint8_t* data, __unused size_t len, __unused bool final) {} virtual void handleUpload(
__unused AsyncWebServerRequest *request, __unused const String &filename, __unused size_t index, __unused uint8_t *data, __unused size_t len,
__unused bool final
) {}
virtual void handleBody(__unused AsyncWebServerRequest *request, __unused uint8_t *data, __unused size_t len, __unused size_t index, __unused size_t total) {} virtual void handleBody(__unused AsyncWebServerRequest *request, __unused uint8_t *data, __unused size_t len, __unused size_t index, __unused size_t total) {}
virtual bool isRequestHandlerTrivial() const { return true; } virtual bool isRequestHandlerTrivial() const {
return true;
}
}; };
/* /*
@ -770,6 +994,8 @@ class AsyncWebServerResponse {
size_t _writtenLength; size_t _writtenLength;
WebResponseState _state; WebResponseState _state;
static bool headerMustBePresentOnce(const String &name);
public: public:
static const char *responseCodeToString(int code); static const char *responseCodeToString(int code);
@ -777,17 +1003,30 @@ class AsyncWebServerResponse {
AsyncWebServerResponse(); AsyncWebServerResponse();
virtual ~AsyncWebServerResponse() {} virtual ~AsyncWebServerResponse() {}
void setCode(int code); void setCode(int code);
int code() const { return _code; } int code() const {
return _code;
}
void setContentLength(size_t len); void setContentLength(size_t len);
void setContentType(const String& type) { setContentType(type.c_str()); } void setContentType(const String &type) {
setContentType(type.c_str());
}
void setContentType(const char *type); void setContentType(const char *type);
bool addHeader(const char *name, const char *value, bool replaceExisting = true); bool addHeader(const char *name, const char *value, bool replaceExisting = true);
bool addHeader(const String& name, const String& value, bool replaceExisting = true) { return addHeader(name.c_str(), value.c_str(), replaceExisting); } bool addHeader(const String &name, const String &value, bool replaceExisting = true) {
bool addHeader(const char* name, long value, bool replaceExisting = true) { return addHeader(name, String(value), replaceExisting); } return addHeader(name.c_str(), value.c_str(), replaceExisting);
bool addHeader(const String& name, long value, bool replaceExisting = true) { return addHeader(name.c_str(), value, replaceExisting); } }
bool addHeader(const char *name, long value, bool replaceExisting = true) {
return addHeader(name, String(value), replaceExisting);
}
bool addHeader(const String &name, long value, bool replaceExisting = true) {
return addHeader(name.c_str(), value, replaceExisting);
}
bool removeHeader(const char *name); bool removeHeader(const char *name);
bool removeHeader(const char *name, const char *value);
const AsyncWebHeader *getHeader(const char *name) const; const AsyncWebHeader *getHeader(const char *name) const;
const std::list<AsyncWebHeader>& getHeaders() const { return _headers; } const std::list<AsyncWebHeader> &getHeaders() const {
return _headers;
}
#ifndef ESP8266 #ifndef ESP8266
[[deprecated("Use instead: _assembleHead(String& buffer, uint8_t version)")]] [[deprecated("Use instead: _assembleHead(String& buffer, uint8_t version)")]]
@ -812,7 +1051,8 @@ class AsyncWebServerResponse {
* */ * */
typedef std::function<void(AsyncWebServerRequest *request)> ArRequestHandlerFunction; typedef std::function<void(AsyncWebServerRequest *request)> ArRequestHandlerFunction;
typedef std::function<void(AsyncWebServerRequest* request, const String& filename, size_t index, uint8_t* data, size_t len, bool final)> ArUploadHandlerFunction; typedef std::function<void(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final)>
ArUploadHandlerFunction;
typedef std::function<void(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total)> ArBodyHandlerFunction; typedef std::function<void(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total)> ArBodyHandlerFunction;
class AsyncWebServer : public AsyncMiddlewareChain { class AsyncWebServer : public AsyncMiddlewareChain {
@ -878,14 +1118,21 @@ class AsyncWebServer : public AsyncMiddlewareChain {
AsyncWebHandler &addHandler(AsyncWebHandler *handler); AsyncWebHandler &addHandler(AsyncWebHandler *handler);
bool removeHandler(AsyncWebHandler *handler); bool removeHandler(AsyncWebHandler *handler);
AsyncCallbackWebHandler& on(const char* uri, ArRequestHandlerFunction onRequest) { return on(uri, HTTP_ANY, onRequest); } AsyncCallbackWebHandler &on(const char *uri, ArRequestHandlerFunction onRequest) {
AsyncCallbackWebHandler& on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload = nullptr, ArBodyHandlerFunction onBody = nullptr); return on(uri, HTTP_ANY, onRequest);
}
AsyncCallbackWebHandler &on(
const char *uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload = nullptr,
ArBodyHandlerFunction onBody = nullptr
);
AsyncStaticWebHandler &serveStatic(const char *uri, fs::FS &fs, const char *path, const char *cache_control = NULL); AsyncStaticWebHandler &serveStatic(const char *uri, fs::FS &fs, const char *path, const char *cache_control = NULL);
void onNotFound(ArRequestHandlerFunction fn); // called when handler is not assigned void onNotFound(ArRequestHandlerFunction fn); // called when handler is not assigned
void onFileUpload(ArUploadHandlerFunction fn); // handle file uploads void onFileUpload(ArUploadHandlerFunction fn); // handle file uploads
void onRequestBody(ArBodyHandlerFunction fn); // handle posts with plain body content (JSON often transmitted this way as a request) void onRequestBody(ArBodyHandlerFunction fn); // handle posts with plain body content (JSON often transmitted this way as a request)
// give access to the handler used to catch all requests, so that middleware can be added to it
AsyncWebHandler &catchAllHandler() const;
void reset(); // remove all writers and handlers, with onNotFound/onFileUpload/onRequestBody void reset(); // remove all writers and handlers, with onNotFound/onFileUpload/onRequestBody
@ -907,8 +1154,12 @@ class DefaultHeaders {
_headers.emplace_back(name, value); _headers.emplace_back(name, value);
} }
ConstIterator begin() const { return _headers.begin(); } ConstIterator begin() const {
ConstIterator end() const { return _headers.end(); } return _headers.begin();
}
ConstIterator end() const {
return _headers.end();
}
DefaultHeaders(DefaultHeaders const &) = delete; DefaultHeaders(DefaultHeaders const &) = delete;
DefaultHeaders &operator=(DefaultHeaders const &) = delete; DefaultHeaders &operator=(DefaultHeaders const &) = delete;

View file

@ -1,11 +1,16 @@
// 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);
@ -14,38 +19,48 @@ void AsyncMiddlewareChain::addMiddleware(ArMiddlewareCallback fn) {
} }
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(
std::remove_if(
_middlewares.begin(), _middlewares.end(),
[middleware](AsyncMiddleware *m) {
if (m == middleware) { if (m == middleware) {
if (m->_freeOnRemoval) if (m->_freeOnRemoval) {
delete m; delete m;
}
return true; return true;
} }
return false; return false;
}), }
_middlewares.end()); ),
_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);
@ -72,38 +87,50 @@ 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());
if (_credentials.length()) {
_hash = true; _hash = true;
return 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());
if (_credentials.length()) {
_hash = true; _hash = true;
return true; return true;
} else {
default:
return false; return false;
} }
default: 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);
} }
@ -113,26 +140,29 @@ void AsyncAuthenticationMiddleware::run(AsyncWebServerRequest* request, ArMiddle
} }
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();
} }
@ -229,8 +259,9 @@ 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);

View file

@ -1,26 +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 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"
@ -32,14 +15,16 @@ 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;
@ -65,7 +50,7 @@ String generateBasicHash(const char* username, const char* password) {
} }
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);
@ -75,8 +60,9 @@ static bool getMD5(uint8_t* data, uint16_t len, char* output) { // 33 bytes or m
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);
@ -99,8 +85,12 @@ String genRandomMD5() {
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;
@ -108,8 +98,12 @@ String genRandomMD5() {
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;
@ -120,27 +114,47 @@ String generateDigestHash(const char* username, const char* password, const char
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;
} }

View file

@ -1,23 +1,5 @@
/* // 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_
@ -26,7 +8,10 @@
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);

View file

@ -1,23 +1,6 @@
/* // 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_
@ -67,7 +50,7 @@ class AsyncStaticWebHandler : public AsyncWebHandler {
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);
@ -86,16 +69,26 @@ class AsyncCallbackWebHandler : public AsyncWebHandler {
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,23 +1,6 @@
/* // 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"
@ -42,10 +25,12 @@ AsyncWebHandler& AsyncWebHandler::setAuthentication(const char* username, const
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,11 +38,13 @@ 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;
@ -105,8 +92,9 @@ AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(time_t last_modifi
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);
} }
@ -124,16 +112,19 @@ 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);
@ -181,6 +172,14 @@ bool AsyncStaticWebHandler::_searchFile(AsyncWebServerRequest* request, const St
// 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;
} }
@ -191,8 +190,9 @@ 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;
} }
@ -212,7 +212,7 @@ void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest* request) {
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];
@ -222,16 +222,21 @@ void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest* request) {
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 {
#if defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
etag = String(request->_tempFile.size());
#else
etag = request->_tempFile.size(); 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;
@ -242,15 +247,24 @@ void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest* request) {
response = new AsyncFileResponse(request->_tempFile, filename, emptyString, false, _callback); response = new AsyncFileResponse(request->_tempFile, filename, emptyString, false, _callback);
} }
if (!response) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
request->abort();
return;
}
response->addHeader(T_ETag, etag.c_str()); response->addHeader(T_ETag, etag.c_str());
if (_last_modified.length()) if (_last_modified.length()) {
response->addHeader(T_Last_Modified, _last_modified.c_str()); response->addHeader(T_Last_Modified, _last_modified.c_str());
if (_cache_control.length()) }
if (_cache_control.length()) {
response->addHeader(T_Cache_Control, _cache_control.c_str()); response->addHeader(T_Cache_Control, _cache_control.c_str());
}
request->send(response); request->send(response);
} }
AsyncStaticWebHandler &AsyncStaticWebHandler::setTemplateProcessor(AwsTemplateProcessor newCallback) { AsyncStaticWebHandler &AsyncStaticWebHandler::setTemplateProcessor(AwsTemplateProcessor newCallback) {
@ -264,8 +278,9 @@ void AsyncCallbackWebHandler::setUri(const String& uri) {
} }
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) {
@ -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,23 +1,6 @@
/* // 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 "WebAuthentication.h" #include "WebAuthentication.h"
#include "WebResponseImpl.h" #include "WebResponseImpl.h"
@ -26,25 +9,84 @@
#define __is_param_char(c) ((c) && ((c) != '{') && ((c) != '[') && ((c) != '&') && ((c) != '=')) #define __is_param_char(c) ((c) && ((c) != '{') && ((c) != '[') && ((c) != '&') && ((c) != '='))
static void doNotDelete(AsyncWebServerRequest *) {}
using namespace asyncsrv; using namespace asyncsrv;
enum { PARSE_REQ_START = 0, enum {
PARSE_REQ_START = 0,
PARSE_REQ_HEADERS = 1, PARSE_REQ_HEADERS = 1,
PARSE_REQ_BODY = 2, PARSE_REQ_BODY = 2,
PARSE_REQ_END = 3, PARSE_REQ_END = 3,
PARSE_REQ_FAIL = 4 }; PARSE_REQ_FAIL = 4
};
AsyncWebServerRequest::AsyncWebServerRequest(AsyncWebServer *s, AsyncClient *c) AsyncWebServerRequest::AsyncWebServerRequest(AsyncWebServer *s, AsyncClient *c)
: _client(c), _server(s), _handler(NULL), _response(NULL), _temp(), _parseState(PARSE_REQ_START), _version(0), _method(HTTP_ANY), _url(), _host(), _contentType(), _boundary(), _authorization(), _reqconntype(RCT_HTTP), _authMethod(AsyncAuthType::AUTH_NONE), _isMultipart(false), _isPlainPost(false), _expectingContinue(false), _contentLength(0), _parsedLength(0), _multiParseState(0), _boundaryPosition(0), _itemStartIndex(0), _itemSize(0), _itemName(), _itemFilename(), _itemType(), _itemValue(), _itemBuffer(0), _itemBufferIndex(0), _itemIsFile(false), _tempObject(NULL) { : _client(c), _server(s), _handler(NULL), _response(NULL), _temp(), _parseState(PARSE_REQ_START), _version(0), _method(HTTP_ANY), _url(), _host(),
c->onError([](void* r, AsyncClient* c, int8_t error) { (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onError(error); }, this); _contentType(), _boundary(), _authorization(), _reqconntype(RCT_HTTP), _authMethod(AsyncAuthType::AUTH_NONE), _isMultipart(false), _isPlainPost(false),
c->onAck([](void* r, AsyncClient* c, size_t len, uint32_t time) { (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onAck(len, time); }, this); _expectingContinue(false), _contentLength(0), _parsedLength(0), _multiParseState(0), _boundaryPosition(0), _itemStartIndex(0), _itemSize(0), _itemName(),
c->onDisconnect([](void* r, AsyncClient* c) { AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onDisconnect(); delete c; }, this); _itemFilename(), _itemType(), _itemValue(), _itemBuffer(0), _itemBufferIndex(0), _itemIsFile(false), _tempObject(NULL) {
c->onTimeout([](void* r, AsyncClient* c, uint32_t time) { (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onTimeout(time); }, this); c->onError(
c->onData([](void* r, AsyncClient* c, void* buf, size_t len) { (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onData(buf, len); }, this); [](void *r, AsyncClient *c, int8_t error) {
c->onPoll([](void* r, AsyncClient* c) { (void)c; AsyncWebServerRequest *req = ( AsyncWebServerRequest*)r; req->_onPoll(); }, this); (void)c;
// log_e("AsyncWebServerRequest::_onError");
AsyncWebServerRequest *req = (AsyncWebServerRequest *)r;
req->_onError(error);
},
this
);
c->onAck(
[](void *r, AsyncClient *c, size_t len, uint32_t time) {
(void)c;
// log_e("AsyncWebServerRequest::_onAck");
AsyncWebServerRequest *req = (AsyncWebServerRequest *)r;
req->_onAck(len, time);
},
this
);
c->onDisconnect(
[](void *r, AsyncClient *c) {
// log_e("AsyncWebServerRequest::_onDisconnect");
AsyncWebServerRequest *req = (AsyncWebServerRequest *)r;
req->_onDisconnect();
delete c;
},
this
);
c->onTimeout(
[](void *r, AsyncClient *c, uint32_t time) {
(void)c;
// log_e("AsyncWebServerRequest::_onTimeout");
AsyncWebServerRequest *req = (AsyncWebServerRequest *)r;
req->_onTimeout(time);
},
this
);
c->onData(
[](void *r, AsyncClient *c, void *buf, size_t len) {
(void)c;
// log_e("AsyncWebServerRequest::_onData");
AsyncWebServerRequest *req = (AsyncWebServerRequest *)r;
req->_onData(buf, len);
},
this
);
c->onPoll(
[](void *r, AsyncClient *c) {
(void)c;
// log_e("AsyncWebServerRequest::_onPoll");
AsyncWebServerRequest *req = (AsyncWebServerRequest *)r;
req->_onPoll();
},
this
);
} }
AsyncWebServerRequest::~AsyncWebServerRequest() { AsyncWebServerRequest::~AsyncWebServerRequest() {
// log_e("AsyncWebServerRequest::~AsyncWebServerRequest");
_this.reset();
_headers.clear(); _headers.clear();
_pathParams.clear(); _pathParams.clear();
@ -74,7 +116,7 @@ void AsyncWebServerRequest::_onData(void* buf, size_t len) {
log_d("SSL/TLS handshake detected: resetting connection"); log_d("SSL/TLS handshake detected: resetting connection");
#endif #endif
_parseState = PARSE_REQ_FAIL; _parseState = PARSE_REQ_FAIL;
_client->abort(); abort();
return; return;
} }
#endif #endif
@ -89,7 +131,7 @@ void AsyncWebServerRequest::_onData(void* buf, size_t len) {
// Check for null characters in header // Check for null characters in header
if (!str[i]) { if (!str[i]) {
_parseState = PARSE_REQ_FAIL; _parseState = PARSE_REQ_FAIL;
_client->abort(); abort();
return; return;
} }
if (str[i] == '\n') { if (str[i] == '\n') {
@ -99,7 +141,14 @@ void AsyncWebServerRequest::_onData(void* buf, size_t len) {
if (i == len) { // No new line, just add the buffer in _temp if (i == len) { // No new line, just add the buffer in _temp
char ch = str[len - 1]; char ch = str[len - 1];
str[len - 1] = 0; str[len - 1] = 0;
_temp.reserve(_temp.length() + len); if (!_temp.reserve(_temp.length() + len)) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
_parseState = PARSE_REQ_FAIL;
abort();
return;
}
_temp.concat(str); _temp.concat(str);
_temp.concat(ch); _temp.concat(ch);
} else { // Found new line - extract it and parse } else { // Found new line - extract it and parse
@ -118,6 +167,8 @@ void AsyncWebServerRequest::_onData(void* buf, size_t len) {
// A handler should be already attached at this point in _parseLine function. // A handler should be already attached at this point in _parseLine function.
// If handler does nothing (_onRequest is NULL), we don't need to really parse the body. // If handler does nothing (_onRequest is NULL), we don't need to really parse the body.
const bool needParse = _handler && !_handler->isRequestHandlerTrivial(); const bool needParse = _handler && !_handler->isRequestHandlerTrivial();
// Discard any bytes after content length; handlers may overrun their buffers
len = std::min(len, _contentLength - _parsedLength);
if (_isMultipart) { if (_isMultipart) {
if (needParse) { if (needParse) {
size_t i; size_t i;
@ -125,16 +176,16 @@ void AsyncWebServerRequest::_onData(void* buf, size_t len) {
_parseMultipartPostByte(((uint8_t *)buf)[i], i == len - 1); _parseMultipartPostByte(((uint8_t *)buf)[i], i == len - 1);
_parsedLength++; _parsedLength++;
} }
} else } else {
_parsedLength += len; _parsedLength += len;
}
} else { } else {
if (_parsedLength == 0) { if (_parsedLength == 0) {
if (_contentType.startsWith(T_app_xform_urlencoded)) { if (_contentType.startsWith(T_app_xform_urlencoded)) {
_isPlainPost = true; _isPlainPost = true;
} else if (_contentType == T_text_plain && __is_param_char(((char *)buf)[0])) { } else if (_contentType == T_text_plain && __is_param_char(((char *)buf)[0])) {
size_t i = 0; size_t i = 0;
while (i < len && __is_param_char(((char*)buf)[i++])) while (i < len && __is_param_char(((char *)buf)[i++]));
;
if (i < len && ((char *)buf)[i - 1] == '=') { if (i < len && ((char *)buf)[i - 1] == '=') {
_isPlainPost = true; _isPlainPost = true;
} }
@ -142,8 +193,9 @@ void AsyncWebServerRequest::_onData(void* buf, size_t len) {
} }
if (!_isPlainPost) { if (!_isPlainPost) {
// ESP_LOGD("AsyncWebServer", "_isPlainPost: %d, _handler: %p", _isPlainPost, _handler); // ESP_LOGD("AsyncWebServer", "_isPlainPost: %d, _handler: %p", _isPlainPost, _handler);
if (_handler) if (_handler) {
_handler->handleBody(this, (uint8_t *)buf, len, _parsedLength, _contentLength); _handler->handleBody(this, (uint8_t *)buf, len, _parsedLength, _contentLength);
}
_parsedLength += len; _parsedLength += len;
} else if (needParse) { } else if (needParse) {
size_t i; size_t i;
@ -157,16 +209,8 @@ void AsyncWebServerRequest::_onData(void* buf, size_t len) {
} }
if (_parsedLength == _contentLength) { if (_parsedLength == _contentLength) {
_parseState = PARSE_REQ_END; _parseState = PARSE_REQ_END;
_server->_runChain(this, [this]() { return _handler ? _handler->_runChain(this, [this]() { _handler->handleRequest(this); }) : send(501); }); _runMiddlewareChain();
if (!_sent) { _send();
if (!_response)
send(501, T_text_plain, "Handler did not handle the request");
else if (!_response->_sourceValid())
send(500, T_text_plain, "Invalid data in handler");
_client->setRxTimeout(0);
_response->_respond(this);
_sent = true;
}
} }
} }
break; break;
@ -233,14 +277,18 @@ void AsyncWebServerRequest::_addGetParams(const String& params) {
size_t start = 0; size_t start = 0;
while (start < params.length()) { while (start < params.length()) {
int end = params.indexOf('&', start); int end = params.indexOf('&', start);
if (end < 0) if (end < 0) {
end = params.length(); end = params.length();
}
int equal = params.indexOf('=', start); int equal = params.indexOf('=', start);
if (equal < 0 || equal > end) if (equal < 0 || equal > end) {
equal = end; equal = end;
String name(params.substring(start, equal)); }
String value(equal + 1 < end ? params.substring(equal + 1, end) : String()); String name = urlDecode(params.substring(start, equal));
_params.emplace_back(urlDecode(name), urlDecode(value)); String value = urlDecode(equal + 1 < end ? params.substring(equal + 1, end) : emptyString);
if (name.length()) {
_params.emplace_back(name, value);
}
start = end + 1; start = end + 1;
} }
} }
@ -280,11 +328,13 @@ bool AsyncWebServerRequest::_parseReqHead() {
_url = urlDecode(u); _url = urlDecode(u);
_addGetParams(g); _addGetParams(g);
if (!_url.length()) if (!_url.length()) {
return false; return false;
}
if (!_temp.startsWith(T_HTTP_1_0)) if (!_temp.startsWith(T_HTTP_1_0)) {
_version = 1; _version = 1;
}
_temp = emptyString; _temp = emptyString;
return true; return true;
@ -344,18 +394,19 @@ bool AsyncWebServerRequest::_parseReqHeader() {
} }
_headers.emplace_back(name, value); _headers.emplace_back(name, value);
} }
#ifndef TARGET_RP2040 #if defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
_temp.clear();
#else
// Ancient PRI core does not have String::clear() method 8-() // Ancient PRI core does not have String::clear() method 8-()
_temp = emptyString; _temp = emptyString;
#else
_temp.clear();
#endif #endif
return true; return true;
} }
void AsyncWebServerRequest::_parsePlainPostChar(uint8_t data) { void AsyncWebServerRequest::_parsePlainPostChar(uint8_t data) {
if (data && (char)data != '&') if (data && (char)data != '&') {
_temp += (char)data; _temp += (char)data;
}
if (!data || (char)data == '&' || _parsedLength == _contentLength) { if (!data || (char)data == '&' || _parsedLength == _contentLength) {
String name(T_BODY); String name(T_BODY);
String value(_temp); String value(_temp);
@ -363,13 +414,16 @@ void AsyncWebServerRequest::_parsePlainPostChar(uint8_t data) {
name = _temp.substring(0, _temp.indexOf('=')); name = _temp.substring(0, _temp.indexOf('='));
value = _temp.substring(_temp.indexOf('=') + 1); value = _temp.substring(_temp.indexOf('=') + 1);
} }
_params.emplace_back(urlDecode(name), urlDecode(value), true); name = urlDecode(name);
if (name.length()) {
_params.emplace_back(name, urlDecode(value), true);
}
#ifndef TARGET_RP2040 #if defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
_temp.clear();
#else
// Ancient PRI core does not have String::clear() method 8-() // Ancient PRI core does not have String::clear() method 8-()
_temp = emptyString; _temp = emptyString;
#else
_temp.clear();
#endif #endif
} }
} }
@ -379,8 +433,9 @@ void AsyncWebServerRequest::_handleUploadByte(uint8_t data, bool last) {
if (last || _itemBufferIndex == RESPONSE_STREAM_BUFFER_SIZE) { if (last || _itemBufferIndex == RESPONSE_STREAM_BUFFER_SIZE) {
// check if authenticated before calling the upload // check if authenticated before calling the upload
if (_handler) if (_handler) {
_handler->handleUpload(this, _itemFilename, _itemSize - _itemBufferIndex, _itemBuffer, _itemBufferIndex, false); _handler->handleUpload(this, _itemFilename, _itemSize - _itemBufferIndex, _itemBuffer, _itemBufferIndex, false);
}
_itemBufferIndex = 0; _itemBufferIndex = 0;
} }
} }
@ -442,8 +497,9 @@ void AsyncWebServerRequest::_parseMultipartPostByte(uint8_t data, bool last) {
_itemIsFile = false; _itemIsFile = false;
} }
} else if (_multiParseState == PARSE_HEADERS) { } else if (_multiParseState == PARSE_HEADERS) {
if ((char)data != '\r' && (char)data != '\n') if ((char)data != '\r' && (char)data != '\n') {
_temp += (char)data; _temp += (char)data;
}
if ((char)data == '\n') { if ((char)data == '\n') {
if (_temp.length()) { if (_temp.length()) {
if (_temp.length() > 12 && _temp.substring(0, 12).equalsIgnoreCase(T_Content_Type)) { if (_temp.length() > 12 && _temp.substring(0, 12).equalsIgnoreCase(T_Content_Type)) {
@ -479,11 +535,16 @@ void AsyncWebServerRequest::_parseMultipartPostByte(uint8_t data, bool last) {
_itemStartIndex = _parsedLength; _itemStartIndex = _parsedLength;
_itemValue = emptyString; _itemValue = emptyString;
if (_itemIsFile) { if (_itemIsFile) {
if (_itemBuffer) if (_itemBuffer) {
free(_itemBuffer); free(_itemBuffer);
}
_itemBuffer = (uint8_t *)malloc(RESPONSE_STREAM_BUFFER_SIZE); _itemBuffer = (uint8_t *)malloc(RESPONSE_STREAM_BUFFER_SIZE);
if (_itemBuffer == NULL) { if (_itemBuffer == NULL) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
_multiParseState = PARSE_ERROR; _multiParseState = PARSE_ERROR;
abort();
return; return;
} }
_itemBufferIndex = 0; _itemBufferIndex = 0;
@ -526,8 +587,9 @@ void AsyncWebServerRequest::_parseMultipartPostByte(uint8_t data, bool last) {
itemWriteByte('-'); itemWriteByte('-');
itemWriteByte('-'); itemWriteByte('-');
uint8_t i; uint8_t i;
for (i = 0; i < _boundaryPosition; i++) for (i = 0; i < _boundaryPosition; i++) {
itemWriteByte(_boundary.c_str()[i]); itemWriteByte(_boundary.c_str()[i]);
}
_parseMultipartPostByte(data, last); _parseMultipartPostByte(data, last);
} else if (_boundaryPosition == _boundary.length() - 1) { } else if (_boundaryPosition == _boundary.length() - 1) {
_multiParseState = DASH3_OR_RETURN2; _multiParseState = DASH3_OR_RETURN2;
@ -535,8 +597,9 @@ void AsyncWebServerRequest::_parseMultipartPostByte(uint8_t data, bool last) {
_params.emplace_back(_itemName, _itemValue, true); _params.emplace_back(_itemName, _itemValue, true);
} else { } else {
if (_itemSize) { if (_itemSize) {
if (_handler) if (_handler) {
_handler->handleUpload(this, _itemFilename, _itemSize - _itemBufferIndex, _itemBuffer, _itemBufferIndex, true); _handler->handleUpload(this, _itemFilename, _itemSize - _itemBufferIndex, _itemBuffer, _itemBufferIndex, true);
}
_itemBufferIndex = 0; _itemBufferIndex = 0;
_params.emplace_back(_itemName, _itemFilename, true, true, _itemSize); _params.emplace_back(_itemName, _itemFilename, true, true, _itemSize);
} }
@ -563,8 +626,9 @@ void AsyncWebServerRequest::_parseMultipartPostByte(uint8_t data, bool last) {
itemWriteByte('-'); itemWriteByte('-');
itemWriteByte('-'); itemWriteByte('-');
uint8_t i; uint8_t i;
for (i = 0; i < _boundary.length(); i++) for (i = 0; i < _boundary.length(); i++) {
itemWriteByte(_boundary.c_str()[i]); itemWriteByte(_boundary.c_str()[i]);
}
_parseMultipartPostByte(data, last); _parseMultipartPostByte(data, last);
} }
} else if (_multiParseState == EXPECT_FEED2) { } else if (_multiParseState == EXPECT_FEED2) {
@ -578,8 +642,9 @@ void AsyncWebServerRequest::_parseMultipartPostByte(uint8_t data, bool last) {
itemWriteByte('-'); itemWriteByte('-');
itemWriteByte('-'); itemWriteByte('-');
uint8_t i; uint8_t i;
for (i = 0; i < _boundary.length(); i++) for (i = 0; i < _boundary.length(); i++) {
itemWriteByte(_boundary.c_str()[i]); itemWriteByte(_boundary.c_str()[i]);
}
itemWriteByte('\r'); itemWriteByte('\r');
_parseMultipartPostByte(data, last); _parseMultipartPostByte(data, last);
} }
@ -590,13 +655,13 @@ void AsyncWebServerRequest::_parseLine() {
if (_parseState == PARSE_REQ_START) { if (_parseState == PARSE_REQ_START) {
if (!_temp.length()) { if (!_temp.length()) {
_parseState = PARSE_REQ_FAIL; _parseState = PARSE_REQ_FAIL;
_client->abort(); abort();
} else { } else {
if (_parseReqHead()) { if (_parseReqHead()) {
_parseState = PARSE_REQ_HEADERS; _parseState = PARSE_REQ_HEADERS;
} else { } else {
_parseState = PARSE_REQ_FAIL; _parseState = PARSE_REQ_FAIL;
_client->abort(); abort();
} }
} }
return; return;
@ -615,19 +680,71 @@ void AsyncWebServerRequest::_parseLine() {
_parseState = PARSE_REQ_BODY; _parseState = PARSE_REQ_BODY;
} else { } else {
_parseState = PARSE_REQ_END; _parseState = PARSE_REQ_END;
_server->_runChain(this, [this]() { return _handler ? _handler->_runChain(this, [this]() { _handler->handleRequest(this); }) : send(501); }); _runMiddlewareChain();
if (!_sent) { _send();
if (!_response) }
} else {
_parseReqHeader();
}
}
}
void AsyncWebServerRequest::_runMiddlewareChain() {
if (_handler && _handler->mustSkipServerMiddlewares()) {
_handler->_runChain(this, [this]() {
_handler->handleRequest(this);
});
} else {
_server->_runChain(this, [this]() {
if (_handler) {
_handler->_runChain(this, [this]() {
_handler->handleRequest(this);
});
}
});
}
}
void AsyncWebServerRequest::_send() {
if (!_sent && !_paused) {
// log_d("AsyncWebServerRequest::_send()");
// user did not create a response ?
if (!_response) {
send(501, T_text_plain, "Handler did not handle the request"); send(501, T_text_plain, "Handler did not handle the request");
else if (!_response->_sourceValid()) }
// response is not valid ?
if (!_response->_sourceValid()) {
send(500, T_text_plain, "Invalid data in handler"); send(500, T_text_plain, "Invalid data in handler");
}
// here, we either have a response give nfrom user or one of the two above
_client->setRxTimeout(0); _client->setRxTimeout(0);
_response->_respond(this); _response->_respond(this);
_sent = true; _sent = true;
} }
} }
} else
_parseReqHeader(); AsyncWebServerRequestPtr AsyncWebServerRequest::pause() {
if (_paused) {
return _this;
}
client()->setRxTimeout(0);
// this shared ptr will hold the request pointer until it gets destroyed following a disconnect.
// this is just used as a holder providing weak observers, so the deleter is a no-op.
_this = std::shared_ptr<AsyncWebServerRequest>(this, doNotDelete);
_paused = true;
return _this;
}
void AsyncWebServerRequest::abort() {
if (!_sent) {
_sent = true;
_paused = false;
_this.reset();
// log_e("AsyncWebServerRequest::abort");
_client->abort();
} }
} }
@ -651,7 +768,9 @@ bool AsyncWebServerRequest::hasHeader(const __FlashStringHelper* data) const {
#endif #endif
const AsyncWebHeader *AsyncWebServerRequest::getHeader(const char *name) const { const AsyncWebHeader *AsyncWebServerRequest::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);
} }
@ -672,23 +791,25 @@ const AsyncWebHeader* AsyncWebServerRequest::getHeader(const __FlashStringHelper
#endif #endif
const AsyncWebHeader *AsyncWebServerRequest::getHeader(size_t num) const { const AsyncWebHeader *AsyncWebServerRequest::getHeader(size_t num) const {
if (num >= _headers.size()) if (num >= _headers.size()) {
return nullptr; return nullptr;
}
return &(*std::next(_headers.cbegin(), num)); return &(*std::next(_headers.cbegin(), num));
} }
size_t AsyncWebServerRequest::getHeaderNames(std::vector<const char *> &names) const { size_t AsyncWebServerRequest::getHeaderNames(std::vector<const char *> &names) const {
const size_t size = _headers.size(); const size_t size = names.size();
names.reserve(size);
for (const auto &h : _headers) { for (const auto &h : _headers) {
names.push_back(h.name().c_str()); names.push_back(h.name().c_str());
} }
return size; return names.size() - size;
} }
bool AsyncWebServerRequest::removeHeader(const char *name) { bool AsyncWebServerRequest::removeHeader(const char *name) {
const size_t size = _headers.size(); const size_t size = _headers.size();
_headers.remove_if([name](const AsyncWebHeader& header) { return header.name().equalsIgnoreCase(name); }); _headers.remove_if([name](const AsyncWebHeader &header) {
return header.name().equalsIgnoreCase(name);
});
return size != _headers.size(); return size != _headers.size();
} }
@ -721,8 +842,9 @@ const AsyncWebParameter* AsyncWebServerRequest::getParam(const __FlashStringHelp
#endif #endif
const AsyncWebParameter *AsyncWebServerRequest::getParam(size_t num) const { const AsyncWebParameter *AsyncWebServerRequest::getParam(size_t num) const {
if (num >= _params.size()) if (num >= _params.size()) {
return nullptr; return nullptr;
}
return &(*std::next(_params.cbegin(), num)); return &(*std::next(_params.cbegin(), num));
} }
@ -748,24 +870,30 @@ double AsyncWebServerRequest::getAttribute(const char* name, double defaultValue
} }
AsyncWebServerResponse *AsyncWebServerRequest::beginResponse(int code, const char *contentType, const char *content, AwsTemplateProcessor callback) { AsyncWebServerResponse *AsyncWebServerRequest::beginResponse(int code, const char *contentType, const char *content, AwsTemplateProcessor callback) {
if (callback) if (callback) {
return new AsyncProgmemResponse(code, contentType, (const uint8_t *)content, strlen(content), callback); return new AsyncProgmemResponse(code, contentType, (const uint8_t *)content, strlen(content), callback);
}
return new AsyncBasicResponse(code, contentType, content); return new AsyncBasicResponse(code, contentType, content);
} }
AsyncWebServerResponse* AsyncWebServerRequest::beginResponse(int code, const char* contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback) { AsyncWebServerResponse *
AsyncWebServerRequest::beginResponse(int code, const char *contentType, const uint8_t *content, size_t len, AwsTemplateProcessor callback) {
return new AsyncProgmemResponse(code, contentType, content, len, callback); return new AsyncProgmemResponse(code, contentType, content, len, callback);
} }
AsyncWebServerResponse* AsyncWebServerRequest::beginResponse(FS& fs, const String& path, const char* contentType, bool download, AwsTemplateProcessor callback) { AsyncWebServerResponse *
if (fs.exists(path) || (!download && fs.exists(path + T__gz))) AsyncWebServerRequest::beginResponse(FS &fs, const String &path, const char *contentType, bool download, AwsTemplateProcessor callback) {
if (fs.exists(path) || (!download && fs.exists(path + T__gz))) {
return new AsyncFileResponse(fs, path, contentType, download, callback); return new AsyncFileResponse(fs, path, contentType, download, callback);
}
return NULL; return NULL;
} }
AsyncWebServerResponse* AsyncWebServerRequest::beginResponse(File content, const String& path, const char* contentType, bool download, AwsTemplateProcessor callback) { AsyncWebServerResponse *
if (content == true) AsyncWebServerRequest::beginResponse(File content, const String &path, const char *contentType, bool download, AwsTemplateProcessor callback) {
if (content == true) {
return new AsyncFileResponse(content, path, contentType, download, callback); return new AsyncFileResponse(content, path, contentType, download, callback);
}
return NULL; return NULL;
} }
@ -773,13 +901,16 @@ AsyncWebServerResponse* AsyncWebServerRequest::beginResponse(Stream& stream, con
return new AsyncStreamResponse(stream, contentType, len, callback); return new AsyncStreamResponse(stream, contentType, len, callback);
} }
AsyncWebServerResponse* AsyncWebServerRequest::beginResponse(const char* contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback) { AsyncWebServerResponse *
AsyncWebServerRequest::beginResponse(const char *contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback) {
return new AsyncCallbackResponse(contentType, len, callback, templateCallback); return new AsyncCallbackResponse(contentType, len, callback, templateCallback);
} }
AsyncWebServerResponse* AsyncWebServerRequest::beginChunkedResponse(const char* contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback) { AsyncWebServerResponse *
if (_version) AsyncWebServerRequest::beginChunkedResponse(const char *contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback) {
if (_version) {
return new AsyncChunkedResponse(contentType, callback, templateCallback); return new AsyncChunkedResponse(contentType, callback, templateCallback);
}
return new AsyncCallbackResponse(contentType, 0, callback, templateCallback); return new AsyncCallbackResponse(contentType, 0, callback, templateCallback);
} }
@ -792,11 +923,22 @@ AsyncWebServerResponse* AsyncWebServerRequest::beginResponse_P(int code, const S
} }
void AsyncWebServerRequest::send(AsyncWebServerResponse *response) { void AsyncWebServerRequest::send(AsyncWebServerResponse *response) {
if (_sent) // request is already sent on the wire ?
if (_sent) {
return; return;
if (_response) }
// if we already had a response, delete it and replace it with the new one
if (_response) {
delete _response; delete _response;
}
_response = response; _response = response;
// if request was paused, we need to send the response now
if (_paused) {
_paused = false;
_send();
}
} }
void AsyncWebServerRequest::redirect(const char *url, int code) { void AsyncWebServerRequest::redirect(const char *url, int code) {
@ -807,30 +949,34 @@ void AsyncWebServerRequest::redirect(const char* url, int code) {
bool AsyncWebServerRequest::authenticate(const char *username, const char *password, const char *realm, bool passwordIsHash) const { bool AsyncWebServerRequest::authenticate(const char *username, const char *password, const char *realm, bool passwordIsHash) const {
if (_authorization.length()) { if (_authorization.length()) {
if (_authMethod == AsyncAuthType::AUTH_DIGEST) if (_authMethod == AsyncAuthType::AUTH_DIGEST) {
return checkDigestAuthentication(_authorization.c_str(), methodToString(), username, password, realm, passwordIsHash, NULL, NULL, NULL); return checkDigestAuthentication(_authorization.c_str(), methodToString(), username, password, realm, passwordIsHash, NULL, NULL, NULL);
else if (!passwordIsHash) } else if (!passwordIsHash) {
return checkBasicAuthentication(_authorization.c_str(), username, password); return checkBasicAuthentication(_authorization.c_str(), username, password);
else } else {
return _authorization.equals(password); return _authorization.equals(password);
} }
}
return false; return false;
} }
bool AsyncWebServerRequest::authenticate(const char *hash) const { bool AsyncWebServerRequest::authenticate(const char *hash) const {
if (!_authorization.length() || hash == NULL) if (!_authorization.length() || hash == NULL) {
return false; return false;
}
if (_authMethod == AsyncAuthType::AUTH_DIGEST) { if (_authMethod == AsyncAuthType::AUTH_DIGEST) {
String hStr = String(hash); String hStr = String(hash);
int separator = hStr.indexOf(':'); int separator = hStr.indexOf(':');
if (separator <= 0) if (separator <= 0) {
return false; return false;
}
String username = hStr.substring(0, separator); String username = hStr.substring(0, separator);
hStr = hStr.substring(separator + 1); hStr = hStr.substring(separator + 1);
separator = hStr.indexOf(':'); separator = hStr.indexOf(':');
if (separator <= 0) if (separator <= 0) {
return false; return false;
}
String realm = hStr.substring(0, separator); String realm = hStr.substring(0, separator);
hStr = hStr.substring(separator + 1); hStr = hStr.substring(separator + 1);
return checkDigestAuthentication(_authorization.c_str(), methodToString(), username.c_str(), hStr.c_str(), realm.c_str(), true, NULL, NULL, NULL); return checkDigestAuthentication(_authorization.c_str(), methodToString(), username.c_str(), hStr.c_str(), realm.c_str(), true, NULL, NULL, NULL);
@ -841,38 +987,57 @@ bool AsyncWebServerRequest::authenticate(const char* hash) const {
} }
void AsyncWebServerRequest::requestAuthentication(AsyncAuthType method, const char *realm, const char *_authFailMsg) { void AsyncWebServerRequest::requestAuthentication(AsyncAuthType method, const char *realm, const char *_authFailMsg) {
if (!realm) if (!realm) {
realm = T_LOGIN_REQ; realm = T_LOGIN_REQ;
}
AsyncWebServerResponse *r = _authFailMsg ? beginResponse(401, T_text_html, _authFailMsg) : beginResponse(401); AsyncWebServerResponse *r = _authFailMsg ? beginResponse(401, T_text_html, _authFailMsg) : beginResponse(401);
switch (method) { switch (method) {
case AsyncAuthType::AUTH_BASIC: { case AsyncAuthType::AUTH_BASIC:
{
String header; String header;
header.reserve(strlen(T_BASIC_REALM) + strlen(realm) + 1); if (header.reserve(strlen(T_BASIC_REALM) + strlen(realm) + 1)) {
header.concat(T_BASIC_REALM); header.concat(T_BASIC_REALM);
header.concat(realm); header.concat(realm);
header.concat('"'); header.concat('"');
r->addHeader(T_WWW_AUTH, header.c_str()); r->addHeader(T_WWW_AUTH, header.c_str());
} else {
#ifdef ESP32
log_e("Failed to allocate");
#endif
abort();
}
break; break;
} }
case AsyncAuthType::AUTH_DIGEST: { case AsyncAuthType::AUTH_DIGEST:
{
size_t len = strlen(T_DIGEST_) + strlen(T_realm__) + strlen(T_auth_nonce) + 32 + strlen(T__opaque) + 32 + 1; size_t len = strlen(T_DIGEST_) + strlen(T_realm__) + strlen(T_auth_nonce) + 32 + strlen(T__opaque) + 32 + 1;
String header; String header;
header.reserve(len + strlen(realm)); if (header.reserve(len + strlen(realm))) {
const String nonce = genRandomMD5();
const String opaque = genRandomMD5();
if (nonce.length() && opaque.length()) {
header.concat(T_DIGEST_); header.concat(T_DIGEST_);
header.concat(T_realm__); header.concat(T_realm__);
header.concat(realm); header.concat(realm);
header.concat(T_auth_nonce); header.concat(T_auth_nonce);
header.concat(genRandomMD5()); header.concat(nonce);
header.concat(T__opaque); header.concat(T__opaque);
header.concat(genRandomMD5()); header.concat(opaque);
header.concat((char)0x22); // '"' header.concat((char)0x22); // '"'
r->addHeader(T_WWW_AUTH, header.c_str()); r->addHeader(T_WWW_AUTH, header.c_str());
} else {
#ifdef ESP32
log_e("Failed to allocate");
#endif
abort();
}
}
break; break;
} }
default: default: break;
break;
} }
send(r); send(r);
@ -917,7 +1082,12 @@ const String& AsyncWebServerRequest::argName(size_t i) const {
} }
const String &AsyncWebServerRequest::pathArg(size_t i) const { const String &AsyncWebServerRequest::pathArg(size_t i) const {
return i < _pathParams.size() ? _pathParams[i] : emptyString; if (i >= _pathParams.size()) {
return emptyString;
}
auto it = _pathParams.begin();
std::advance(it, i);
return *it;
} }
const String &AsyncWebServerRequest::header(const char *name) const { const String &AsyncWebServerRequest::header(const char *name) const {
@ -946,7 +1116,13 @@ String AsyncWebServerRequest::urlDecode(const String& text) const {
unsigned int len = text.length(); unsigned int len = text.length();
unsigned int i = 0; unsigned int i = 0;
String decoded; String decoded;
decoded.reserve(len); // Allocate the string internal buffer - never longer from source text // Allocate the string internal buffer - never longer from source text
if (!decoded.reserve(len)) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
return emptyString;
}
while (i < len) { while (i < len) {
char decodedChar; char decodedChar;
char encodedChar = text.charAt(i++); char encodedChar = text.charAt(i++);
@ -965,44 +1141,45 @@ String AsyncWebServerRequest::urlDecode(const String& text) const {
} }
const char *AsyncWebServerRequest::methodToString() const { const char *AsyncWebServerRequest::methodToString() const {
if (_method == HTTP_ANY) if (_method == HTTP_ANY) {
return T_ANY; return T_ANY;
if (_method & HTTP_GET) }
if (_method & HTTP_GET) {
return T_GET; return T_GET;
if (_method & HTTP_POST) }
if (_method & HTTP_POST) {
return T_POST; return T_POST;
if (_method & HTTP_DELETE) }
if (_method & HTTP_DELETE) {
return T_DELETE; return T_DELETE;
if (_method & HTTP_PUT) }
if (_method & HTTP_PUT) {
return T_PUT; return T_PUT;
if (_method & HTTP_PATCH) }
if (_method & HTTP_PATCH) {
return T_PATCH; return T_PATCH;
if (_method & HTTP_HEAD) }
if (_method & HTTP_HEAD) {
return T_HEAD; return T_HEAD;
if (_method & HTTP_OPTIONS) }
if (_method & HTTP_OPTIONS) {
return T_OPTIONS; return T_OPTIONS;
}
return T_UNKNOWN; return T_UNKNOWN;
} }
const char *AsyncWebServerRequest::requestedConnTypeToString() const { const char *AsyncWebServerRequest::requestedConnTypeToString() const {
switch (_reqconntype) { switch (_reqconntype) {
case RCT_NOT_USED: case RCT_NOT_USED: return T_RCT_NOT_USED;
return T_RCT_NOT_USED; case RCT_DEFAULT: return T_RCT_DEFAULT;
case RCT_DEFAULT: case RCT_HTTP: return T_RCT_HTTP;
return T_RCT_DEFAULT; case RCT_WS: return T_RCT_WS;
case RCT_HTTP: case RCT_EVENT: return T_RCT_EVENT;
return T_RCT_HTTP; default: return T_ERROR;
case RCT_WS:
return T_RCT_WS;
case RCT_EVENT:
return T_RCT_EVENT;
default:
return T_ERROR;
} }
} }
bool AsyncWebServerRequest::isExpectedRequestedConnType(RequestedConnectionType erct1, RequestedConnectionType erct2, RequestedConnectionType erct3) const { bool AsyncWebServerRequest::isExpectedRequestedConnType(RequestedConnectionType erct1, RequestedConnectionType erct2, RequestedConnectionType erct3) const {
return ((erct1 != RCT_NOT_USED) && (erct1 == _reqconntype)) || return ((erct1 != RCT_NOT_USED) && (erct1 == _reqconntype)) || ((erct2 != RCT_NOT_USED) && (erct2 == _reqconntype))
((erct2 != RCT_NOT_USED) && (erct2 == _reqconntype)) || || ((erct3 != RCT_NOT_USED) && (erct3 == _reqconntype));
((erct3 != RCT_NOT_USED) && (erct3 == _reqconntype));
} }

View file

@ -1,23 +1,6 @@
/* // 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_
@ -39,16 +22,19 @@ class AsyncBasicResponse : public AsyncWebServerResponse {
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)
: AsyncBasicResponse(code, contentType.c_str(), content.c_str()) {}
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;
bool _sourceValid() const override final { return true; } 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};
@ -70,8 +56,12 @@ class AsyncAbstractResponse : public AsyncWebServerResponse {
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
@ -90,11 +80,19 @@ class AsyncFileResponse : public AsyncAbstractResponse {
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); } );
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; size_t _fillBuffer(uint8_t *buf, size_t maxLen) override final;
}; };
@ -104,8 +102,11 @@ class AsyncStreamResponse : public AsyncAbstractResponse {
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) {}
bool _sourceValid() const override final {
return !!(_content);
}
size_t _fillBuffer(uint8_t *buf, size_t maxLen) override final; size_t _fillBuffer(uint8_t *buf, size_t maxLen) override final;
}; };
@ -116,8 +117,11 @@ class AsyncCallbackResponse : public AsyncAbstractResponse {
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) {}
bool _sourceValid() const override final {
return !!(_content);
}
size_t _fillBuffer(uint8_t *buf, size_t maxLen) override final; size_t _fillBuffer(uint8_t *buf, size_t maxLen) override final;
}; };
@ -128,8 +132,11 @@ class AsyncChunkedResponse : public AsyncAbstractResponse {
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) {}
bool _sourceValid() const override final {
return !!(_content);
}
size_t _fillBuffer(uint8_t *buf, size_t maxLen) override final; size_t _fillBuffer(uint8_t *buf, size_t maxLen) override final;
}; };
@ -140,8 +147,11 @@ class AsyncProgmemResponse : public AsyncAbstractResponse {
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) {}
bool _sourceValid() const override final {
return true;
}
size_t _fillBuffer(uint8_t *buf, size_t maxLen) override final; size_t _fillBuffer(uint8_t *buf, size_t maxLen) override final;
}; };
@ -152,7 +162,9 @@ class AsyncResponseStream : public AsyncAbstractResponse, public Print {
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 {
return (_state < RESPONSE_END);
}
size_t _fillBuffer(uint8_t *buf, size_t maxLen) override final; size_t _fillBuffer(uint8_t *buf, size_t maxLen) override final;
size_t write(const uint8_t *data, size_t len); size_t write(const uint8_t *data, size_t len);
size_t write(uint8_t data); size_t write(uint8_t data);

View file

@ -1,23 +1,6 @@
/* // 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"
@ -26,9 +9,11 @@ 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;
} }
@ -39,118 +24,93 @@ 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),
_state(RESPONSE_SETUP) {
for (const auto &header : DefaultHeaders::Instance()) { 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;
} }
@ -159,10 +119,21 @@ bool AsyncWebServerResponse::removeHeader(const char* name) {
} }
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::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) { 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)) {
@ -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
} }
} }
} }
@ -185,21 +158,25 @@ bool AsyncWebServerResponse::addHeader(const char* name, const char* value, bool
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);
@ -233,10 +210,18 @@ 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 {
return _state > RESPONSE_WAIT_ACK;
}
bool AsyncWebServerResponse::_failed() const {
return _state == RESPONSE_FAILED;
}
bool AsyncWebServerResponse::_sourceValid() const {
return false;
}
void AsyncWebServerResponse::_respond(AsyncWebServerRequest *request) { void AsyncWebServerResponse::_respond(AsyncWebServerRequest *request) {
_state = RESPONSE_END; _state = RESPONSE_END;
request->client()->close(); request->client()->close();
@ -257,9 +242,10 @@ AsyncBasicResponse::AsyncBasicResponse(int code, const char* contentType, const
_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);
} }
@ -355,8 +341,9 @@ 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) {
@ -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
@ -420,7 +408,10 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest* request, size_t len, u
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;
} }
@ -481,10 +472,11 @@ 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;
} }
@ -502,16 +494,19 @@ size_t AsyncAbstractResponse::_readDataFromCacheOrContent(uint8_t* data, const s
} }
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;
@ -529,9 +524,11 @@ size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size
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) {
@ -547,10 +544,12 @@ size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size
_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.
@ -568,10 +567,11 @@ size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size
// 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)
@ -604,48 +604,50 @@ void AsyncFileResponse::_setContentTypeFromPath(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;
@ -660,10 +662,11 @@ 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];
@ -679,7 +682,8 @@ 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;
@ -693,10 +697,11 @@ 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];
@ -729,8 +734,9 @@ 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,12 +744,14 @@ 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;
} }
@ -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;
@ -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;
@ -810,7 +820,11 @@ AsyncResponseStream::AsyncResponseStream(const char* contentType, size_t bufferS
_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) {
@ -818,8 +832,9 @@ size_t AsyncResponseStream::_fillBuffer(uint8_t* buf, size_t 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,23 +1,6 @@
/* // 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"
@ -45,14 +28,13 @@ 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) {
@ -60,14 +42,15 @@ AsyncWebServer::AsyncWebServer(uint16_t port)
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) {
@ -138,6 +121,8 @@ void AsyncWebServer::_handleDisconnect(AsyncWebServerRequest* request) {
} }
void AsyncWebServer::_rewriteRequest(AsyncWebServerRequest *request) { void AsyncWebServer::_rewriteRequest(AsyncWebServerRequest *request) {
// the last rewrite that matches the request will be used
// we do not break the loop to allow for multiple rewrites to be applied and only the last one to be used (allows overriding)
for (const auto &r : _rewrites) { for (const auto &r : _rewrites) {
if (r->match(request)) { if (r->match(request)) {
request->_url = r->toUrl(); request->_url = r->toUrl();
@ -157,7 +142,9 @@ 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(
const char *uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody
) {
AsyncCallbackWebHandler *handler = new AsyncCallbackWebHandler(); AsyncCallbackWebHandler *handler = new AsyncCallbackWebHandler();
handler->setUri(uri); handler->setUri(uri);
handler->setMethod(method); handler->setMethod(method);
@ -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,3 +1,6 @@
// 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 {
@ -25,6 +28,7 @@ namespace asyncsrv {
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_Content_Location = "content-location";
static constexpr const char *T_Cookie = "cookie"; static constexpr const char *T_Cookie = "cookie";
static constexpr const char *T_CORS_ACAC = "access-control-allow-credentials"; static constexpr const char *T_CORS_ACAC = "access-control-allow-credentials";
static constexpr const char *T_CORS_ACAH = "access-control-allow-headers"; static constexpr const char *T_CORS_ACAH = "access-control-allow-headers";
@ -33,6 +37,7 @@ namespace asyncsrv {
static constexpr const char *T_CORS_ACMA = "access-control-max-age"; static constexpr const char *T_CORS_ACMA = "access-control-max-age";
static constexpr const char *T_CORS_O = "origin"; static constexpr const char *T_CORS_O = "origin";
static constexpr const char *T_data_ = "data: "; static constexpr const char *T_data_ = "data: ";
static constexpr const char *T_Date = "date";
static constexpr const char *T_DIGEST = "digest"; static constexpr const char *T_DIGEST = "digest";
static constexpr const char *T_DIGEST_ = "digest "; static constexpr const char *T_DIGEST_ = "digest ";
static constexpr const char *T_ETag = "etag"; static constexpr const char *T_ETag = "etag";
@ -68,6 +73,7 @@ namespace asyncsrv {
static constexpr const char *T_nn = "\n\n"; static constexpr const char *T_nn = "\n\n";
static constexpr const char *T_rn = "\r\n"; static constexpr const char *T_rn = "\r\n";
static constexpr const char *T_rnrn = "\r\n\r\n"; static constexpr const char *T_rnrn = "\r\n\r\n";
static constexpr const char *T_Server = "server";
static constexpr const char *T_Transfer_Encoding = "transfer-encoding"; static constexpr const char *T_Transfer_Encoding = "transfer-encoding";
static constexpr const char *T_TRUE = "true"; static constexpr const char *T_TRUE = "true";
static constexpr const char *T_UPGRADE = "upgrade"; static constexpr const char *T_UPGRADE = "upgrade";
@ -96,7 +102,7 @@ namespace asyncsrv {
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";
@ -136,7 +142,7 @@ namespace asyncsrv {
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";
@ -180,4 +186,8 @@ namespace asyncsrv {
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);
}; };