mirror of
https://github.com/dalathegreat/Battery-Emulator.git
synced 2025-10-06 03:50:13 +02:00
Merge branch 'main' into feature/egmp-soc-estimation
This commit is contained in:
commit
5800a3c281
67 changed files with 5808 additions and 7041 deletions
|
@ -49,12 +49,13 @@
|
|||
volatile unsigned long long bmsResetTimeOffset = 0;
|
||||
|
||||
// The current software version, shown on webserver
|
||||
const char* version_number = "8.8.dev";
|
||||
const char* version_number = "8.9.dev";
|
||||
|
||||
// Interval settings
|
||||
uint16_t intervalUpdateValues = INTERVAL_1_S; // Interval at which to update inverter values / Modbus registers
|
||||
unsigned long previousMillis10ms = 0;
|
||||
unsigned long previousMillisUpdateVal = 0;
|
||||
unsigned long lastMillisOverflowCheck = 0;
|
||||
// Task time measurement for debugging and for setting CPU load events
|
||||
int64_t core_task_time_us;
|
||||
MyTimer core_task_timer_10s(INTERVAL_10_S);
|
||||
|
@ -146,17 +147,8 @@ void setup() {
|
|||
#endif
|
||||
}
|
||||
|
||||
// Perform main program functions
|
||||
void loop() {
|
||||
START_TIME_MEASUREMENT(loop_func);
|
||||
run_event_handling();
|
||||
END_TIME_MEASUREMENT_MAX(loop_func, datalayer.system.status.loop_task_10s_max_us);
|
||||
#ifdef FUNCTION_TIME_MEASUREMENT
|
||||
if (loop_task_timer_10s.elapsed()) {
|
||||
datalayer.system.status.loop_task_10s_max_us = 0;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
// Loop empty, all functionality runs in tasks
|
||||
void loop() {}
|
||||
|
||||
#if defined(LOG_CAN_TO_SD) || defined(LOG_TO_SD)
|
||||
void logging_loop(void* task_time_us) {
|
||||
|
@ -499,6 +491,11 @@ void update_calculated_values() {
|
|||
datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery2.status.remaining_capacity_Wh;
|
||||
}
|
||||
#endif // DOUBLE_BATTERY
|
||||
// Check if millis() has overflowed. Used in events to keep better track of time
|
||||
if (millis() < lastMillisOverflowCheck) { // Overflow detected
|
||||
datalayer.system.status.millisrolloverCount++;
|
||||
}
|
||||
lastMillisOverflowCheck = millis();
|
||||
}
|
||||
|
||||
void update_values_inverter() {
|
||||
|
|
|
@ -1060,10 +1060,6 @@ void transmit_can_battery() {
|
|||
case CELL_VOLTAGE_CELLNO:
|
||||
current_cell_polled++;
|
||||
if (current_cell_polled > 96) {
|
||||
datalayer.battery.info.number_of_cells = 97;
|
||||
#ifdef DOUBLE_BATTERY
|
||||
datalayer.battery2.info.number_of_cells = 97;
|
||||
#endif
|
||||
cmdState = CELL_VOLTAGE_CELLNO_LAST;
|
||||
} else {
|
||||
cmdState = CELL_VOLTAGE_CELLNO;
|
||||
|
@ -1150,13 +1146,14 @@ void setup_battery(void) { // Performs one time setup at startup
|
|||
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_60AH;
|
||||
datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
|
||||
datalayer.system.status.battery_allows_contactor_closing = true;
|
||||
|
||||
datalayer.battery.info.number_of_cells = NUMBER_OF_CELLS;
|
||||
#ifdef DOUBLE_BATTERY
|
||||
datalayer.battery2.info.max_design_voltage_dV = datalayer.battery.info.max_design_voltage_dV;
|
||||
datalayer.battery2.info.min_design_voltage_dV = datalayer.battery.info.min_design_voltage_dV;
|
||||
datalayer.battery2.info.max_cell_voltage_deviation_mV = datalayer.battery.info.max_cell_voltage_deviation_mV;
|
||||
datalayer.battery2.status.voltage_dV =
|
||||
0; //Init voltage to 0 to allow contactor check to operate without fear of default values colliding
|
||||
datalayer.battery2.info.number_of_cells = NUMBER_OF_CELLS;
|
||||
#endif
|
||||
pinMode(WUP_PIN1, OUTPUT);
|
||||
digitalWrite(WUP_PIN1, HIGH); // Wake up the battery
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#define MIN_PACK_VOLTAGE_94AH 2590 // Discharge stops if pack voltage exceeds this value
|
||||
#define MAX_PACK_VOLTAGE_120AH 4030 // Charge stops if pack voltage exceeds this value
|
||||
#define MIN_PACK_VOLTAGE_120AH 2680 // Discharge stops if pack voltage exceeds this value
|
||||
#define NUMBER_OF_CELLS 96
|
||||
void setup_battery(void);
|
||||
void transmit_can_frame(CAN_frame* tx_frame, int interface);
|
||||
|
||||
|
|
|
@ -33,7 +33,6 @@ static uint8_t battery_request_idx = 0;
|
|||
static uint8_t rxConsecutiveFrames = 0;
|
||||
static uint16_t min_max_voltage[2]; //contains cell min[0] and max[1] values in mV
|
||||
static uint8_t cellcounter = 0;
|
||||
static uint32_t remaining_capacity = 0;
|
||||
static uint16_t cell_voltages[108]; //array with all the cellvoltages
|
||||
static bool startedUp = false;
|
||||
static uint8_t DTC_reset_counter = 0;
|
||||
|
@ -134,12 +133,13 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
datalayer_extended.VolvoPolestar.UserRequestDTCreadout = false;
|
||||
}
|
||||
|
||||
remaining_capacity = (78200 - CHARGE_ENERGY);
|
||||
datalayer.battery.status.remaining_capacity_Wh = (datalayer.battery.info.total_capacity_Wh - CHARGE_ENERGY);
|
||||
|
||||
//datalayer.battery.status.real_soc = SOC_BMS; // Use BMS reported SOC, havent figured out how to get the BMS to calibrate empty/full yet
|
||||
SOC_CALC = remaining_capacity / 78; // Use calculated SOC based on remaining_capacity
|
||||
// Use calculated SOC based on remaining_capacity
|
||||
SOC_CALC = (datalayer.battery.status.remaining_capacity_Wh / (datalayer.battery.info.total_capacity_Wh / 1000));
|
||||
|
||||
datalayer.battery.status.real_soc = SOC_CALC * 10;
|
||||
datalayer.battery.status.real_soc = SOC_CALC * 10; //Add one decimal to make it pptt
|
||||
|
||||
if (BATT_U > MAX_U) // Protect if overcharged
|
||||
{
|
||||
|
@ -151,7 +151,6 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
|
||||
datalayer.battery.status.voltage_dV = BATT_U * 10;
|
||||
datalayer.battery.status.current_dA = BATT_I * 10;
|
||||
datalayer.battery.status.remaining_capacity_Wh = remaining_capacity;
|
||||
|
||||
datalayer.battery.status.max_discharge_power_W = HvBattPwrLimDchaSlowAgi * 1000; //kW to W
|
||||
datalayer.battery.status.max_charge_power_W = HvBattPwrLimChrgSlowAgi * 1000; //kW to W
|
||||
|
@ -166,6 +165,22 @@ void update_values_battery() { //This function maps all the values fetched via
|
|||
datalayer.battery.status.cell_voltages_mV[i] = cell_voltages[i];
|
||||
}
|
||||
|
||||
//If we have enough cell values populated (atleast 96 read) AND number_of_cells not initialized yet
|
||||
if (cell_voltages[95] > 0 && datalayer.battery.info.number_of_cells == 0) {
|
||||
// We can determine whether we have 96S or 108S battery
|
||||
if (datalayer.battery.status.cell_voltages_mV[107] > 0) {
|
||||
datalayer.battery.info.number_of_cells = 108;
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_108S_DV;
|
||||
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_108S_DV;
|
||||
datalayer.battery.info.total_capacity_Wh = 78200;
|
||||
} else {
|
||||
datalayer.battery.info.number_of_cells = 96;
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_96S_DV;
|
||||
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_96S_DV;
|
||||
datalayer.battery.info.total_capacity_Wh = 69511;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG_LOG
|
||||
logging.print("BMS reported SOC%: ");
|
||||
logging.println(SOC_BMS);
|
||||
|
@ -463,11 +478,12 @@ void transmit_can_battery() {
|
|||
}
|
||||
|
||||
void setup_battery(void) { // Performs one time setup at startup
|
||||
strncpy(datalayer.system.info.battery_protocol, "Volvo / Polestar 78kWh battery", 63);
|
||||
strncpy(datalayer.system.info.battery_protocol, "Volvo / Polestar 69/78kWh SPA battery", 63);
|
||||
datalayer.system.info.battery_protocol[63] = '\0';
|
||||
datalayer.battery.info.number_of_cells = 108;
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_DV;
|
||||
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_DV;
|
||||
datalayer.battery.info.number_of_cells = 0; // Initializes when all cells have been read
|
||||
datalayer.battery.info.total_capacity_Wh = 78200; //Startout in 78kWh mode (This value used for SOC calc)
|
||||
datalayer.battery.info.max_design_voltage_dV = MAX_PACK_VOLTAGE_108S_DV; //Startout with max allowed range
|
||||
datalayer.battery.info.min_design_voltage_dV = MIN_PACK_VOLTAGE_96S_DV; //Startout with min allowed range
|
||||
datalayer.battery.info.max_cell_voltage_mV = MAX_CELL_VOLTAGE_MV;
|
||||
datalayer.battery.info.min_cell_voltage_mV = MIN_CELL_VOLTAGE_MV;
|
||||
datalayer.battery.info.max_cell_voltage_deviation_mV = MAX_CELL_DEVIATION_MV;
|
||||
|
|
|
@ -4,8 +4,10 @@
|
|||
#include "../include.h"
|
||||
|
||||
#define BATTERY_SELECTED
|
||||
#define MAX_PACK_VOLTAGE_DV 4540 //5000 = 500.0V
|
||||
#define MIN_PACK_VOLTAGE_DV 2938
|
||||
#define MAX_PACK_VOLTAGE_108S_DV 4540
|
||||
#define MIN_PACK_VOLTAGE_108S_DV 2938
|
||||
#define MAX_PACK_VOLTAGE_96S_DV 4030
|
||||
#define MIN_PACK_VOLTAGE_96S_DV 2620
|
||||
#define MAX_CELL_DEVIATION_MV 250
|
||||
#define MAX_CELL_VOLTAGE_MV 4210 //Battery is put into emergency stop if one cell goes over this value
|
||||
#define MIN_CELL_VOLTAGE_MV 2700 //Battery is put into emergency stop if one cell goes below this value
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
CAN_device_t CAN_cfg; // CAN Config
|
||||
const int rx_queue_size = 10; // Receive Queue size
|
||||
volatile bool send_ok_native = 0;
|
||||
volatile bool send_ok_2515 = 0;
|
||||
volatile bool send_ok_2518 = 0;
|
||||
|
||||
|
@ -146,7 +147,10 @@ void transmit_can_frame(CAN_frame* tx_frame, int interface) {
|
|||
for (uint8_t i = 0; i < tx_frame->DLC; i++) {
|
||||
frame.data.u8[i] = tx_frame->data.u8[i];
|
||||
}
|
||||
ESP32Can.CANWriteFrame(&frame);
|
||||
send_ok_native = ESP32Can.CANWriteFrame(&frame);
|
||||
if (!send_ok_native) {
|
||||
datalayer.system.info.can_native_send_fail = true;
|
||||
}
|
||||
break;
|
||||
case CAN_ADDON_MCP2515: {
|
||||
#ifdef CAN_ADDON
|
||||
|
@ -162,9 +166,7 @@ void transmit_can_frame(CAN_frame* tx_frame, int interface) {
|
|||
|
||||
send_ok_2515 = can.tryToSend(MCP2515Frame);
|
||||
if (!send_ok_2515) {
|
||||
set_event(EVENT_CAN_BUFFER_FULL, interface);
|
||||
} else {
|
||||
clear_event(EVENT_CAN_BUFFER_FULL);
|
||||
datalayer.system.info.can_2515_send_fail = true;
|
||||
}
|
||||
#else // Interface not compiled, and settings try to use it
|
||||
set_event(EVENT_INTERFACE_MISSING, interface);
|
||||
|
@ -187,9 +189,7 @@ void transmit_can_frame(CAN_frame* tx_frame, int interface) {
|
|||
}
|
||||
send_ok_2518 = canfd.tryToSend(MCP2518Frame);
|
||||
if (!send_ok_2518) {
|
||||
set_event(EVENT_CANFD_BUFFER_FULL, interface);
|
||||
} else {
|
||||
clear_event(EVENT_CANFD_BUFFER_FULL);
|
||||
datalayer.system.info.can_2518_send_fail = true;
|
||||
}
|
||||
#else // Interface not compiled, and settings try to use it
|
||||
set_event(EVENT_INTERFACE_MISSING, interface);
|
||||
|
|
|
@ -228,10 +228,18 @@ typedef struct {
|
|||
uint8_t can_replay_interface = CAN_NATIVE;
|
||||
/** bool, determines if CAN replay should loop or not */
|
||||
bool loop_playback = false;
|
||||
/** bool, Native CAN failed to send flag */
|
||||
bool can_native_send_fail = false;
|
||||
/** bool, MCP2515 CAN failed to send flag */
|
||||
bool can_2515_send_fail = false;
|
||||
/** uint16_t, MCP2518 CANFD failed to send flag */
|
||||
bool can_2518_send_fail = false;
|
||||
|
||||
} DATALAYER_SYSTEM_INFO_TYPE;
|
||||
|
||||
typedef struct {
|
||||
/** Millis rollover count. Increments every 49.7 days. Used for keeping track on events */
|
||||
uint8_t millisrolloverCount = 0;
|
||||
#ifdef FUNCTION_TIME_MEASUREMENT
|
||||
/** Core task measurement variable */
|
||||
int64_t core_task_max_us = 0;
|
||||
|
@ -241,8 +249,6 @@ typedef struct {
|
|||
int64_t mqtt_task_10s_max_us = 0;
|
||||
/** Wifi sub-task measurement variable, reset each 10 seconds */
|
||||
int64_t wifi_task_10s_max_us = 0;
|
||||
/** loop() task measurement variable, reset each 10 seconds */
|
||||
int64_t loop_task_10s_max_us = 0;
|
||||
|
||||
/** OTA handling function measurement variable */
|
||||
int64_t time_ota_us = 0;
|
||||
|
|
|
@ -19,6 +19,26 @@ battery_pause_status emulator_pause_status = NORMAL;
|
|||
//battery pause status end
|
||||
|
||||
void update_machineryprotection() {
|
||||
// Check health status of CAN interfaces
|
||||
if (datalayer.system.info.can_native_send_fail) {
|
||||
set_event(EVENT_CAN_NATIVE_TX_FAILURE, 0);
|
||||
datalayer.system.info.can_native_send_fail = false;
|
||||
} else {
|
||||
clear_event(EVENT_CAN_NATIVE_TX_FAILURE);
|
||||
}
|
||||
if (datalayer.system.info.can_2515_send_fail) {
|
||||
set_event(EVENT_CAN_BUFFER_FULL, 0);
|
||||
datalayer.system.info.can_2515_send_fail = false;
|
||||
} else {
|
||||
clear_event(EVENT_CAN_BUFFER_FULL);
|
||||
}
|
||||
if (datalayer.system.info.can_2518_send_fail) {
|
||||
set_event(EVENT_CANFD_BUFFER_FULL, 0);
|
||||
datalayer.system.info.can_2518_send_fail = false;
|
||||
} else {
|
||||
clear_event(EVENT_CANFD_BUFFER_FULL);
|
||||
}
|
||||
|
||||
// Start checking that the battery is within reason. Incase we see any funny business, raise an event!
|
||||
|
||||
// Pause function is on OR we have a critical fault event active
|
||||
|
|
|
@ -58,8 +58,6 @@ static EVENT_TYPE events;
|
|||
static const char* EVENTS_ENUM_TYPE_STRING[] = {EVENTS_ENUM_TYPE(GENERATE_STRING)};
|
||||
static const char* EVENTS_LEVEL_TYPE_STRING[] = {EVENTS_LEVEL_TYPE(GENERATE_STRING)};
|
||||
|
||||
static uint32_t lastMillis = millis();
|
||||
|
||||
/* Local function prototypes */
|
||||
static void set_event(EVENTS_ENUM_TYPE event, uint8_t data, bool latched);
|
||||
static void update_event_level(void);
|
||||
|
@ -67,21 +65,6 @@ static void update_bms_status(void);
|
|||
static void log_event(EVENTS_ENUM_TYPE event, uint8_t millisrolloverCount, uint32_t timestamp, uint8_t data);
|
||||
static void print_event_log(void);
|
||||
|
||||
uint8_t millisrolloverCount = 0;
|
||||
|
||||
/* Exported functions */
|
||||
|
||||
/* Main execution function, should handle various continuous functionality */
|
||||
void run_event_handling(void) {
|
||||
uint32_t currentMillis = millis();
|
||||
if (currentMillis < lastMillis) { // Overflow detected
|
||||
millisrolloverCount++;
|
||||
}
|
||||
lastMillis = currentMillis;
|
||||
|
||||
update_event_level();
|
||||
}
|
||||
|
||||
/* Initialization function */
|
||||
void init_events(void) {
|
||||
|
||||
|
@ -136,7 +119,7 @@ void init_events(void) {
|
|||
events.entries[EVENT_CAN_BUFFER_FULL].level = EVENT_LEVEL_WARNING;
|
||||
events.entries[EVENT_CAN_OVERRUN].level = EVENT_LEVEL_INFO;
|
||||
events.entries[EVENT_CAN_CORRUPTED_WARNING].level = EVENT_LEVEL_WARNING;
|
||||
events.entries[EVENT_CAN_NATIVE_TX_FAILURE].level = EVENT_LEVEL_ERROR;
|
||||
events.entries[EVENT_CAN_NATIVE_TX_FAILURE].level = EVENT_LEVEL_WARNING;
|
||||
events.entries[EVENT_CAN_BATTERY_MISSING].level = EVENT_LEVEL_ERROR;
|
||||
events.entries[EVENT_CAN_BATTERY2_MISSING].level = EVENT_LEVEL_WARNING;
|
||||
events.entries[EVENT_CAN_CHARGER_MISSING].level = EVENT_LEVEL_INFO;
|
||||
|
@ -275,9 +258,9 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) {
|
|||
case EVENT_CANMCP2515_INIT_FAILURE:
|
||||
return "CAN-MCP addon initialization failed. Check hardware";
|
||||
case EVENT_CANFD_BUFFER_FULL:
|
||||
return "MCP2518FD buffer overflowed. Some CAN messages were not sent. Contact developers.";
|
||||
return "MCP2518FD message failed to send. Buffer full or no one on the bus to ACK the message!";
|
||||
case EVENT_CAN_BUFFER_FULL:
|
||||
return "MCP2515 buffer overflowed. Some CAN messages were not sent. Contact developers.";
|
||||
return "MCP2515 message failed to send. Buffer full or no one on the bus to ACK the message!";
|
||||
case EVENT_CAN_OVERRUN:
|
||||
return "CAN message failed to send within defined time. Contact developers, CPU load might be too high.";
|
||||
case EVENT_CAN_CORRUPTED_WARNING:
|
||||
|
@ -510,7 +493,7 @@ static void set_event(EVENTS_ENUM_TYPE event, uint8_t data, bool latched) {
|
|||
|
||||
// We should set the event, update event info
|
||||
events.entries[event].timestamp = millis();
|
||||
events.entries[event].millisrolloverCount = millisrolloverCount;
|
||||
events.entries[event].millisrolloverCount = datalayer.system.status.millisrolloverCount;
|
||||
events.entries[event].data = data;
|
||||
// Check if the event is latching
|
||||
events.entries[event].state = latched ? EVENT_STATE_ACTIVE_LATCHED : EVENT_STATE_ACTIVE;
|
||||
|
@ -583,8 +566,10 @@ static void log_event(EVENTS_ENUM_TYPE event, uint8_t millisrolloverCount, uint3
|
|||
int entry_address = EE_EVENT_ENTRY_START_ADDRESS + EE_EVENT_ENTRY_SIZE * events.event_log_head_index;
|
||||
|
||||
// Prepare an event block to write
|
||||
EVENT_LOG_ENTRY_TYPE entry = {
|
||||
.event = event, .millisrolloverCount = millisrolloverCount, .timestamp = timestamp, .data = data};
|
||||
EVENT_LOG_ENTRY_TYPE entry = {.event = event,
|
||||
.millisrolloverCount = datalayer.system.status.millisrolloverCount,
|
||||
.timestamp = timestamp,
|
||||
.data = data};
|
||||
|
||||
// Put the event in (what I guess is) the RAM EEPROM mirror, or write buffer
|
||||
EEPROM.put(entry_address, entry);
|
||||
|
|
|
@ -159,8 +159,6 @@ struct EventData {
|
|||
const EVENTS_STRUCT_TYPE* event_pointer;
|
||||
};
|
||||
|
||||
extern uint8_t millisrolloverCount; // number of times millis rollovers
|
||||
|
||||
const char* get_event_enum_string(EVENTS_ENUM_TYPE event);
|
||||
const char* get_event_message_string(EVENTS_ENUM_TYPE event);
|
||||
const char* get_event_level_string(EVENTS_ENUM_TYPE event);
|
||||
|
@ -177,8 +175,6 @@ void set_event_MQTTpublished(EVENTS_ENUM_TYPE event);
|
|||
|
||||
const EVENTS_STRUCT_TYPE* get_event_pointer(EVENTS_ENUM_TYPE event);
|
||||
|
||||
void run_event_handling(void);
|
||||
|
||||
void run_sequence_on_target(void);
|
||||
|
||||
bool compareEventsByTimestampAsc(const EventData& a, const EventData& b);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include "events_html.h"
|
||||
#include "../../datalayer/datalayer.h"
|
||||
|
||||
const char EVENTS_HTML_START[] = R"=====(
|
||||
<style>body{background-color:#000;color:#fff}.event-log{display:flex;flex-direction:column}.event{display:flex;flex-wrap:wrap;border:1px solid #fff;padding:10px}.event>div{flex:1;min-width:100px;max-width:90%;word-break:break-word}</style><div style="background-color:#303e47;padding:10px;margin-bottom:10px;border-radius:25px"><div class="event-log"><div class="event" style="background-color:#1e2c33;font-weight:700"><div>Event Type</div><div>Severity</div><div>Last Event</div><div>Count</div><div>Data</div><div>Message</div></div>
|
||||
|
@ -48,7 +49,7 @@ String events_processor(const String& var) {
|
|||
content.concat("<div class='event'>");
|
||||
content.concat("<div>" + String(get_event_enum_string(event_handle)) + "</div>");
|
||||
content.concat("<div>" + String(get_event_level_string(event_handle)) + "</div>");
|
||||
content.concat("<div class='sec-ago'>" + String(millisrolloverCount) + ";" +
|
||||
content.concat("<div class='sec-ago'>" + String(datalayer.system.status.millisrolloverCount) + ";" +
|
||||
String(timestamp_now - event_pointer->timestamp) + "</div>");
|
||||
content.concat("<div>" + String(event_pointer->occurences) + "</div>");
|
||||
content.concat("<div>" + String(event_pointer->data) + "</div>");
|
||||
|
|
|
@ -940,8 +940,6 @@ String processor(const String& var) {
|
|||
content +=
|
||||
"<h4>WIFI function (MQTT task) max load last 10 s: " + String(datalayer.system.status.wifi_task_10s_max_us) +
|
||||
" us</h4>";
|
||||
content +=
|
||||
"<h4>loop() task max load last 10 s: " + String(datalayer.system.status.loop_task_10s_max_us) + " us</h4>";
|
||||
content += "<h4>Max load @ worst case execution of core task:</h4>";
|
||||
content += "<h4>10ms function timing: " + String(datalayer.system.status.time_snap_10ms_us) + " us</h4>";
|
||||
content += "<h4>Values function timing: " + String(datalayer.system.status.time_snap_values_us) + " us</h4>";
|
||||
|
|
|
@ -4,10 +4,10 @@
|
|||
#include <Preferences.h>
|
||||
#include <WiFi.h>
|
||||
#include "../../include.h"
|
||||
#include "../../lib/ESP32Async-AsyncTCP/src/AsyncTCP.h"
|
||||
#include "../../lib/ESP32Async-ESPAsyncWebServer/src/ESPAsyncWebServer.h"
|
||||
#include "../../lib/YiannisBourkelis-Uptime-Library/src/uptime_formatter.h"
|
||||
#include "../../lib/ayushsharma82-ElegantOTA/src/ElegantOTA.h"
|
||||
#include "../../lib/mathieucarbou-AsyncTCPSock/src/AsyncTCP.h"
|
||||
|
||||
extern const char* version_number; // The current software version, shown on webserver
|
||||
|
||||
|
|
|
@ -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)
|
|
@ -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.
|
|
@ -1,55 +0,0 @@
|
|||

|
||||
|
||||
# AsyncTCP
|
||||
|
||||
[](https://opensource.org/license/lgpl-3-0/)
|
||||
[](https://github.com/ESP32Async/AsyncTCP/actions/workflows/ci.yml)
|
||||
[](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
|
|
@ -1,3 +0,0 @@
|
|||
COMPONENT_ADD_INCLUDEDIRS := src
|
||||
COMPONENT_SRCDIRS := src
|
||||
CXXFLAGS += -fno-rtti
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -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
|
@ -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_ */
|
|
@ -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
|
|
@ -6,12 +6,4 @@ set(COMPONENT_ADD_INCLUDEDIRS
|
|||
"src"
|
||||
)
|
||||
|
||||
set(COMPONENT_REQUIRES
|
||||
"arduino-esp32"
|
||||
"AsyncTCP"
|
||||
)
|
||||
|
||||
register_component()
|
||||
|
||||
target_compile_definitions(${COMPONENT_TARGET} PUBLIC -DESP32)
|
||||
target_compile_options(${COMPONENT_TARGET} PRIVATE -fno-rtti)
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
identity and expression, level of experience, education, socioeconomic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "ESPAsyncWebServer",
|
||||
"version": "3.6.2",
|
||||
"version": "3.7.2",
|
||||
"description": "Asynchronous HTTP and WebSocket Server Library for ESP32, ESP8266 and RP2040. Supports: WebSocket, SSE, Authentication, Arduino Json 7, File Upload, Static File serving, URL Rewrite, URL Redirect, etc.",
|
||||
"keywords": "http,async,websocket,webserver",
|
||||
"homepage": "https://github.com/ESP32Async/ESPAsyncWebServer",
|
||||
|
@ -24,7 +24,7 @@
|
|||
{
|
||||
"owner": "ESP32Async",
|
||||
"name": "AsyncTCP",
|
||||
"version": "^3.3.2",
|
||||
"version": "^3.3.6",
|
||||
"platforms": "espressif32"
|
||||
},
|
||||
{
|
||||
|
@ -38,9 +38,9 @@
|
|||
"platforms": "espressif8266"
|
||||
},
|
||||
{
|
||||
"owner": "khoih-prog",
|
||||
"name": "AsyncTCP_RP2040W",
|
||||
"version": "^1.2.0",
|
||||
"owner": "ayushsharma82",
|
||||
"name": "RPAsyncTCP",
|
||||
"version": "^1.3.1",
|
||||
"platforms": "raspberrypi"
|
||||
}
|
||||
],
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
name=ESP Async WebServer
|
||||
includes=ESPAsyncWebServer.h
|
||||
version=3.6.2
|
||||
version=3.7.2
|
||||
author=ESP32Async
|
||||
maintainer=ESP32Async
|
||||
sentence=Asynchronous HTTP and WebSocket Server Library for ESP32, ESP8266 and RP2040
|
||||
|
@ -8,4 +8,4 @@ paragraph=Supports: WebSocket, SSE, Authentication, Arduino Json 7, File Upload,
|
|||
category=Other
|
||||
url=https://github.com/ESP32Async/ESPAsyncWebServer
|
||||
architectures=*
|
||||
license=LGPL-3.0
|
||||
license=LGPL-3.0
|
||||
|
|
|
@ -1,25 +1,9 @@
|
|||
/*
|
||||
Asynchronous WebServer library for Espressif MCUs
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
|
||||
|
||||
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
#include "Arduino.h"
|
||||
#if defined(ESP32)
|
||||
#include <rom/ets_sys.h>
|
||||
#include <rom/ets_sys.h>
|
||||
#endif
|
||||
#include "AsyncEventSource.h"
|
||||
|
||||
|
@ -27,46 +11,54 @@
|
|||
|
||||
using namespace asyncsrv;
|
||||
|
||||
static String generateEventMessage(const char* message, const char* event, uint32_t id, uint32_t reconnect) {
|
||||
static String generateEventMessage(const char *message, const char *event, uint32_t id, uint32_t reconnect) {
|
||||
String str;
|
||||
size_t len{0};
|
||||
if (message)
|
||||
if (message) {
|
||||
len += strlen(message);
|
||||
}
|
||||
|
||||
if (event)
|
||||
if (event) {
|
||||
len += strlen(event);
|
||||
}
|
||||
|
||||
len += 42; // give it some overhead
|
||||
len += 42; // give it some overhead
|
||||
|
||||
str.reserve(len);
|
||||
if (!str.reserve(len)) {
|
||||
#ifdef ESP32
|
||||
log_e("Failed to allocate");
|
||||
#endif
|
||||
return emptyString;
|
||||
}
|
||||
|
||||
if (reconnect) {
|
||||
str += T_retry_;
|
||||
str += reconnect;
|
||||
str += ASYNC_SSE_NEW_LINE_CHAR; // '\n'
|
||||
str += ASYNC_SSE_NEW_LINE_CHAR; // '\n'
|
||||
}
|
||||
|
||||
if (id) {
|
||||
str += T_id__;
|
||||
str += id;
|
||||
str += ASYNC_SSE_NEW_LINE_CHAR; // '\n'
|
||||
str += ASYNC_SSE_NEW_LINE_CHAR; // '\n'
|
||||
}
|
||||
|
||||
if (event != NULL) {
|
||||
str += T_event_;
|
||||
str += event;
|
||||
str += ASYNC_SSE_NEW_LINE_CHAR; // '\n'
|
||||
str += ASYNC_SSE_NEW_LINE_CHAR; // '\n'
|
||||
}
|
||||
|
||||
if (!message)
|
||||
if (!message) {
|
||||
return str;
|
||||
}
|
||||
|
||||
size_t messageLen = strlen(message);
|
||||
char* lineStart = (char*)message;
|
||||
char* lineEnd;
|
||||
char *lineStart = (char *)message;
|
||||
char *lineEnd;
|
||||
do {
|
||||
char* nextN = strchr(lineStart, '\n');
|
||||
char* nextR = strchr(lineStart, '\r');
|
||||
char *nextN = strchr(lineStart, '\n');
|
||||
char *nextR = strchr(lineStart, '\r');
|
||||
if (nextN == NULL && nextR == NULL) {
|
||||
// a message is a single-line string
|
||||
str += T_data_;
|
||||
|
@ -76,10 +68,10 @@ static String generateEventMessage(const char* message, const char* event, uint3
|
|||
}
|
||||
|
||||
// a message is a multi-line string
|
||||
char* nextLine = NULL;
|
||||
if (nextN != NULL && nextR != NULL) { // windows line-ending \r\n
|
||||
char *nextLine = NULL;
|
||||
if (nextN != NULL && nextR != NULL) { // windows line-ending \r\n
|
||||
if (nextR + 1 == nextN) {
|
||||
// normal \r\n sequense
|
||||
// normal \r\n sequence
|
||||
lineEnd = nextR;
|
||||
nextLine = nextN + 1;
|
||||
} else {
|
||||
|
@ -87,23 +79,23 @@ static String generateEventMessage(const char* message, const char* event, uint3
|
|||
lineEnd = std::min(nextR, nextN);
|
||||
nextLine = lineEnd + 1;
|
||||
}
|
||||
} else if (nextN != NULL) { // Unix/Mac OS X LF
|
||||
} else if (nextN != NULL) { // Unix/Mac OS X LF
|
||||
lineEnd = nextN;
|
||||
nextLine = nextN + 1;
|
||||
} else { // some ancient garbage
|
||||
} else { // some ancient garbage
|
||||
lineEnd = nextR;
|
||||
nextLine = nextR + 1;
|
||||
}
|
||||
|
||||
str += T_data_;
|
||||
str.concat(lineStart, lineEnd - lineStart);
|
||||
str += ASYNC_SSE_NEW_LINE_CHAR; // \n
|
||||
str += ASYNC_SSE_NEW_LINE_CHAR; // \n
|
||||
|
||||
lineStart = nextLine;
|
||||
} while (lineStart < ((char*)message + messageLen));
|
||||
} while (lineStart < ((char *)message + messageLen));
|
||||
|
||||
// append another \n to terminate message
|
||||
str += ASYNC_SSE_NEW_LINE_CHAR; // '\n'
|
||||
str += ASYNC_SSE_NEW_LINE_CHAR; // '\n'
|
||||
|
||||
return str;
|
||||
}
|
||||
|
@ -123,9 +115,10 @@ size_t AsyncEventSourceMessage::ack(size_t len, __attribute__((unused)) uint32_t
|
|||
return 0;
|
||||
}
|
||||
|
||||
size_t AsyncEventSourceMessage::write(AsyncClient* client) {
|
||||
if (!client)
|
||||
size_t AsyncEventSourceMessage::write(AsyncClient *client) {
|
||||
if (!client) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (_sent >= _data->length() || !client->canSend()) {
|
||||
return 0;
|
||||
|
@ -143,31 +136,54 @@ size_t AsyncEventSourceMessage::write(AsyncClient* client) {
|
|||
|
||||
So let's just keep it enforced ASYNC_WRITE_FLAG_COPY and keep in mind that there is no zero-copy
|
||||
*/
|
||||
size_t written = client->add(_data->c_str() + _sent, len, ASYNC_WRITE_FLAG_COPY); // ASYNC_WRITE_FLAG_MORE
|
||||
size_t written = client->add(_data->c_str() + _sent, len, ASYNC_WRITE_FLAG_COPY); // ASYNC_WRITE_FLAG_MORE
|
||||
_sent += written;
|
||||
return written;
|
||||
}
|
||||
|
||||
size_t AsyncEventSourceMessage::send(AsyncClient* client) {
|
||||
size_t AsyncEventSourceMessage::send(AsyncClient *client) {
|
||||
size_t sent = write(client);
|
||||
return sent && client->send() ? sent : 0;
|
||||
}
|
||||
|
||||
// Client
|
||||
|
||||
AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest* request, AsyncEventSource* server)
|
||||
: _client(request->client()), _server(server) {
|
||||
AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server) : _client(request->client()), _server(server) {
|
||||
|
||||
if (request->hasHeader(T_Last_Event_ID))
|
||||
if (request->hasHeader(T_Last_Event_ID)) {
|
||||
_lastId = atoi(request->getHeader(T_Last_Event_ID)->value().c_str());
|
||||
}
|
||||
|
||||
_client->setRxTimeout(0);
|
||||
_client->onError(NULL, NULL);
|
||||
_client->onAck([](void* r, AsyncClient* c, size_t len, uint32_t time) { (void)c; static_cast<AsyncEventSourceClient*>(r)->_onAck(len, time); }, this);
|
||||
_client->onPoll([](void* r, AsyncClient* c) { (void)c; static_cast<AsyncEventSourceClient*>(r)->_onPoll(); }, this);
|
||||
_client->onAck(
|
||||
[](void *r, AsyncClient *c, size_t len, uint32_t time) {
|
||||
(void)c;
|
||||
static_cast<AsyncEventSourceClient *>(r)->_onAck(len, time);
|
||||
},
|
||||
this
|
||||
);
|
||||
_client->onPoll(
|
||||
[](void *r, AsyncClient *c) {
|
||||
(void)c;
|
||||
static_cast<AsyncEventSourceClient *>(r)->_onPoll();
|
||||
},
|
||||
this
|
||||
);
|
||||
_client->onData(NULL, NULL);
|
||||
_client->onTimeout([this](void* r, AsyncClient* c __attribute__((unused)), uint32_t time) { static_cast<AsyncEventSourceClient*>(r)->_onTimeout(time); }, this);
|
||||
_client->onDisconnect([this](void* r, AsyncClient* c) { static_cast<AsyncEventSourceClient*>(r)->_onDisconnect(); delete c; }, this);
|
||||
_client->onTimeout(
|
||||
[this](void *r, AsyncClient *c __attribute__((unused)), uint32_t time) {
|
||||
static_cast<AsyncEventSourceClient *>(r)->_onTimeout(time);
|
||||
},
|
||||
this
|
||||
);
|
||||
_client->onDisconnect(
|
||||
[this](void *r, AsyncClient *c) {
|
||||
static_cast<AsyncEventSourceClient *>(r)->_onDisconnect();
|
||||
delete c;
|
||||
},
|
||||
this
|
||||
);
|
||||
|
||||
_server->_addClient(this);
|
||||
delete request;
|
||||
|
@ -183,7 +199,7 @@ AsyncEventSourceClient::~AsyncEventSourceClient() {
|
|||
close();
|
||||
}
|
||||
|
||||
bool AsyncEventSourceClient::_queueMessage(const char* message, size_t len) {
|
||||
bool AsyncEventSourceClient::_queueMessage(const char *message, size_t len) {
|
||||
if (_messageQueue.size() >= SSE_MAX_QUEUED_MESSAGES) {
|
||||
#ifdef ESP8266
|
||||
ets_printf(String(F("ERROR: Too many messages queued\n")).c_str());
|
||||
|
@ -213,7 +229,7 @@ bool AsyncEventSourceClient::_queueMessage(const char* message, size_t len) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool AsyncEventSourceClient::_queueMessage(AsyncEvent_SharedData_t&& msg) {
|
||||
bool AsyncEventSourceClient::_queueMessage(AsyncEvent_SharedData_t &&msg) {
|
||||
if (_messageQueue.size() >= SSE_MAX_QUEUED_MESSAGES) {
|
||||
#ifdef ESP8266
|
||||
ets_printf(String(F("ERROR: Too many messages queued\n")).c_str());
|
||||
|
@ -249,10 +265,11 @@ void AsyncEventSourceClient::_onAck(size_t len __attribute__((unused)), uint32_t
|
|||
#endif
|
||||
|
||||
// adjust in-flight len
|
||||
if (len < _inflight)
|
||||
if (len < _inflight) {
|
||||
_inflight -= len;
|
||||
else
|
||||
} else {
|
||||
_inflight = 0;
|
||||
}
|
||||
|
||||
// acknowledge as much messages's data as we got confirmed len from a AsyncTCP
|
||||
while (len && _messageQueue.size()) {
|
||||
|
@ -264,8 +281,9 @@ void AsyncEventSourceClient::_onAck(size_t len __attribute__((unused)), uint32_t
|
|||
}
|
||||
|
||||
// try to send another batch of data
|
||||
if (_messageQueue.size())
|
||||
if (_messageQueue.size()) {
|
||||
_runQueue();
|
||||
}
|
||||
}
|
||||
|
||||
void AsyncEventSourceClient::_onPoll() {
|
||||
|
@ -279,31 +297,36 @@ void AsyncEventSourceClient::_onPoll() {
|
|||
}
|
||||
|
||||
void AsyncEventSourceClient::_onTimeout(uint32_t time __attribute__((unused))) {
|
||||
if (_client)
|
||||
if (_client) {
|
||||
_client->close(true);
|
||||
}
|
||||
}
|
||||
|
||||
void AsyncEventSourceClient::_onDisconnect() {
|
||||
if (!_client)
|
||||
if (!_client) {
|
||||
return;
|
||||
}
|
||||
_client = nullptr;
|
||||
_server->_handleDisconnect(this);
|
||||
}
|
||||
|
||||
void AsyncEventSourceClient::close() {
|
||||
if (_client)
|
||||
if (_client) {
|
||||
_client->close();
|
||||
}
|
||||
}
|
||||
|
||||
bool AsyncEventSourceClient::send(const char* message, const char* event, uint32_t id, uint32_t reconnect) {
|
||||
if (!connected())
|
||||
bool AsyncEventSourceClient::send(const char *message, const char *event, uint32_t id, uint32_t reconnect) {
|
||||
if (!connected()) {
|
||||
return false;
|
||||
}
|
||||
return _queueMessage(std::make_shared<String>(generateEventMessage(message, event, id, reconnect)));
|
||||
}
|
||||
|
||||
void AsyncEventSourceClient::_runQueue() {
|
||||
if (!_client)
|
||||
if (!_client) {
|
||||
return;
|
||||
}
|
||||
|
||||
// there is no need to lock the mutex here, 'cause all the calls to this method must be already lock'ed
|
||||
size_t total_bytes_written = 0;
|
||||
|
@ -320,45 +343,52 @@ void AsyncEventSourceClient::_runQueue() {
|
|||
}
|
||||
|
||||
// flush socket
|
||||
if (total_bytes_written)
|
||||
if (total_bytes_written) {
|
||||
_client->send();
|
||||
}
|
||||
}
|
||||
|
||||
void AsyncEventSourceClient::set_max_inflight_bytes(size_t value) {
|
||||
if (value >= SSE_MIN_INFLIGH && value <= SSE_MAX_INFLIGH)
|
||||
if (value >= SSE_MIN_INFLIGH && value <= SSE_MAX_INFLIGH) {
|
||||
_max_inflight = value;
|
||||
}
|
||||
}
|
||||
|
||||
/* AsyncEventSource */
|
||||
|
||||
void AsyncEventSource::authorizeConnect(ArAuthorizeConnectHandler cb) {
|
||||
AsyncAuthorizationMiddleware* m = new AsyncAuthorizationMiddleware(401, cb);
|
||||
AsyncAuthorizationMiddleware *m = new AsyncAuthorizationMiddleware(401, cb);
|
||||
m->_freeOnRemoval = true;
|
||||
addMiddleware(m);
|
||||
}
|
||||
|
||||
void AsyncEventSource::_addClient(AsyncEventSourceClient* client) {
|
||||
if (!client)
|
||||
void AsyncEventSource::_addClient(AsyncEventSourceClient *client) {
|
||||
if (!client) {
|
||||
return;
|
||||
}
|
||||
#ifdef ESP32
|
||||
std::lock_guard<std::mutex> lock(_client_queue_lock);
|
||||
#endif
|
||||
_clients.emplace_back(client);
|
||||
if (_connectcb)
|
||||
if (_connectcb) {
|
||||
_connectcb(client);
|
||||
}
|
||||
|
||||
_adjust_inflight_window();
|
||||
}
|
||||
|
||||
void AsyncEventSource::_handleDisconnect(AsyncEventSourceClient* client) {
|
||||
if (_disconnectcb)
|
||||
void AsyncEventSource::_handleDisconnect(AsyncEventSourceClient *client) {
|
||||
if (_disconnectcb) {
|
||||
_disconnectcb(client);
|
||||
}
|
||||
#ifdef ESP32
|
||||
std::lock_guard<std::mutex> lock(_client_queue_lock);
|
||||
#endif
|
||||
for (auto i = _clients.begin(); i != _clients.end(); ++i) {
|
||||
if (i->get() == client)
|
||||
if (i->get() == client) {
|
||||
_clients.erase(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
_adjust_inflight_window();
|
||||
}
|
||||
|
@ -370,9 +400,10 @@ void AsyncEventSource::close() {
|
|||
#ifdef ESP32
|
||||
std::lock_guard<std::mutex> lock(_client_queue_lock);
|
||||
#endif
|
||||
for (const auto& c : _clients) {
|
||||
if (c->connected())
|
||||
for (const auto &c : _clients) {
|
||||
if (c->connected()) {
|
||||
c->close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -383,31 +414,32 @@ size_t AsyncEventSource::avgPacketsWaiting() const {
|
|||
#ifdef ESP32
|
||||
std::lock_guard<std::mutex> lock(_client_queue_lock);
|
||||
#endif
|
||||
if (!_clients.size())
|
||||
if (!_clients.size()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (const auto& c : _clients) {
|
||||
for (const auto &c : _clients) {
|
||||
if (c->connected()) {
|
||||
aql += c->packetsWaiting();
|
||||
++nConnectedClients;
|
||||
}
|
||||
}
|
||||
return ((aql) + (nConnectedClients / 2)) / (nConnectedClients); // round up
|
||||
return ((aql) + (nConnectedClients / 2)) / (nConnectedClients); // round up
|
||||
}
|
||||
|
||||
AsyncEventSource::SendStatus AsyncEventSource::send(
|
||||
const char* message, const char* event, uint32_t id, uint32_t reconnect) {
|
||||
AsyncEventSource::SendStatus AsyncEventSource::send(const char *message, const char *event, uint32_t id, uint32_t reconnect) {
|
||||
AsyncEvent_SharedData_t shared_msg = std::make_shared<String>(generateEventMessage(message, event, id, reconnect));
|
||||
#ifdef ESP32
|
||||
std::lock_guard<std::mutex> lock(_client_queue_lock);
|
||||
#endif
|
||||
size_t hits = 0;
|
||||
size_t miss = 0;
|
||||
for (const auto& c : _clients) {
|
||||
if (c->write(shared_msg))
|
||||
for (const auto &c : _clients) {
|
||||
if (c->write(shared_msg)) {
|
||||
++hits;
|
||||
else
|
||||
} else {
|
||||
++miss;
|
||||
}
|
||||
}
|
||||
return hits == 0 ? DISCARDED : (miss == 0 ? ENQUEUED : PARTIALLY_ENQUEUED);
|
||||
}
|
||||
|
@ -417,33 +449,36 @@ size_t AsyncEventSource::count() const {
|
|||
std::lock_guard<std::mutex> lock(_client_queue_lock);
|
||||
#endif
|
||||
size_t n_clients{0};
|
||||
for (const auto& i : _clients)
|
||||
if (i->connected())
|
||||
for (const auto &i : _clients) {
|
||||
if (i->connected()) {
|
||||
++n_clients;
|
||||
}
|
||||
}
|
||||
|
||||
return n_clients;
|
||||
}
|
||||
|
||||
bool AsyncEventSource::canHandle(AsyncWebServerRequest* request) const {
|
||||
bool AsyncEventSource::canHandle(AsyncWebServerRequest *request) const {
|
||||
return request->isSSE() && request->url().equals(_url);
|
||||
}
|
||||
|
||||
void AsyncEventSource::handleRequest(AsyncWebServerRequest* request) {
|
||||
void AsyncEventSource::handleRequest(AsyncWebServerRequest *request) {
|
||||
request->send(new AsyncEventSourceResponse(this));
|
||||
}
|
||||
|
||||
void AsyncEventSource::_adjust_inflight_window() {
|
||||
if (_clients.size()) {
|
||||
size_t inflight = SSE_MAX_INFLIGH / _clients.size();
|
||||
for (const auto& c : _clients)
|
||||
for (const auto &c : _clients) {
|
||||
c->set_max_inflight_bytes(inflight);
|
||||
}
|
||||
// Serial.printf("adjusted inflight to: %u\n", inflight);
|
||||
}
|
||||
}
|
||||
|
||||
/* Response */
|
||||
|
||||
AsyncEventSourceResponse::AsyncEventSourceResponse(AsyncEventSource* server) {
|
||||
AsyncEventSourceResponse::AsyncEventSourceResponse(AsyncEventSource *server) {
|
||||
_server = server;
|
||||
_code = 200;
|
||||
_contentType = T_text_event_stream;
|
||||
|
@ -452,14 +487,14 @@ AsyncEventSourceResponse::AsyncEventSourceResponse(AsyncEventSource* server) {
|
|||
addHeader(T_Connection, T_keep_alive);
|
||||
}
|
||||
|
||||
void AsyncEventSourceResponse::_respond(AsyncWebServerRequest* request) {
|
||||
void AsyncEventSourceResponse::_respond(AsyncWebServerRequest *request) {
|
||||
String out;
|
||||
_assembleHead(out, request->version());
|
||||
request->client()->write(out.c_str(), _headLength);
|
||||
_state = RESPONSE_WAIT_ACK;
|
||||
}
|
||||
|
||||
size_t AsyncEventSourceResponse::_ack(AsyncWebServerRequest* request, size_t len, uint32_t time __attribute__((unused))) {
|
||||
size_t AsyncEventSourceResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time __attribute__((unused))) {
|
||||
if (len) {
|
||||
new AsyncEventSourceClient(request, _server);
|
||||
}
|
||||
|
|
|
@ -1,64 +1,48 @@
|
|||
/*
|
||||
Asynchronous WebServer library for Espressif MCUs
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
|
||||
|
||||
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
#ifndef ASYNCEVENTSOURCE_H_
|
||||
#define ASYNCEVENTSOURCE_H_
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#ifdef ESP32
|
||||
#include "../../ESP32Async-AsyncTCP/src/AsyncTCP.h"
|
||||
#include <mutex>
|
||||
#ifndef SSE_MAX_QUEUED_MESSAGES
|
||||
#define SSE_MAX_QUEUED_MESSAGES 32
|
||||
#endif
|
||||
#define SSE_MIN_INFLIGH 2 * 1460 // allow 2 MSS packets
|
||||
#define SSE_MAX_INFLIGH 16 * 1024 // but no more than 16k, no need to blow it, since same data is kept in local Q
|
||||
#include "../../mathieucarbou-AsyncTCPSock/src/AsyncTCP.h"
|
||||
#include <mutex>
|
||||
#ifndef SSE_MAX_QUEUED_MESSAGES
|
||||
#define SSE_MAX_QUEUED_MESSAGES 32
|
||||
#endif
|
||||
#define SSE_MIN_INFLIGH 2 * 1460 // allow 2 MSS packets
|
||||
#define SSE_MAX_INFLIGH 16 * 1024 // but no more than 16k, no need to blow it, since same data is kept in local Q
|
||||
#elif defined(ESP8266)
|
||||
#include <ESPAsyncTCP.h>
|
||||
#ifndef SSE_MAX_QUEUED_MESSAGES
|
||||
#define SSE_MAX_QUEUED_MESSAGES 8
|
||||
#endif
|
||||
#define SSE_MIN_INFLIGH 2 * 1460 // allow 2 MSS packets
|
||||
#define SSE_MAX_INFLIGH 8 * 1024 // but no more than 8k, no need to blow it, since same data is kept in local Q
|
||||
#elif defined(TARGET_RP2040)
|
||||
#include <AsyncTCP_RP2040W.h>
|
||||
#ifndef SSE_MAX_QUEUED_MESSAGES
|
||||
#define SSE_MAX_QUEUED_MESSAGES 32
|
||||
#endif
|
||||
#define SSE_MIN_INFLIGH 2 * 1460 // allow 2 MSS packets
|
||||
#define SSE_MAX_INFLIGH 16 * 1024 // but no more than 16k, no need to blow it, since same data is kept in local Q
|
||||
#include <ESPAsyncTCP.h>
|
||||
#ifndef SSE_MAX_QUEUED_MESSAGES
|
||||
#define SSE_MAX_QUEUED_MESSAGES 8
|
||||
#endif
|
||||
#define SSE_MIN_INFLIGH 2 * 1460 // allow 2 MSS packets
|
||||
#define SSE_MAX_INFLIGH 8 * 1024 // but no more than 8k, no need to blow it, since same data is kept in local Q
|
||||
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
|
||||
#include <RPAsyncTCP.h>
|
||||
#ifndef SSE_MAX_QUEUED_MESSAGES
|
||||
#define SSE_MAX_QUEUED_MESSAGES 32
|
||||
#endif
|
||||
#define SSE_MIN_INFLIGH 2 * 1460 // allow 2 MSS packets
|
||||
#define SSE_MAX_INFLIGH 16 * 1024 // but no more than 16k, no need to blow it, since same data is kept in local Q
|
||||
#endif
|
||||
|
||||
#include "ESPAsyncWebServer.h"
|
||||
|
||||
#ifdef ESP8266
|
||||
#include <Hash.h>
|
||||
#ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library
|
||||
#include <../src/Hash.h>
|
||||
#endif
|
||||
#include <Hash.h>
|
||||
#ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library
|
||||
#include <../src/Hash.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
class AsyncEventSource;
|
||||
class AsyncEventSourceResponse;
|
||||
class AsyncEventSourceClient;
|
||||
using ArEventHandlerFunction = std::function<void(AsyncEventSourceClient* client)>;
|
||||
using ArEventHandlerFunction = std::function<void(AsyncEventSourceClient *client)>;
|
||||
using ArAuthorizeConnectHandler = ArAuthorizeFunction;
|
||||
// shared message object container
|
||||
using AsyncEvent_SharedData_t = std::shared_ptr<String>;
|
||||
|
@ -69,55 +53,67 @@ using AsyncEvent_SharedData_t = std::shared_ptr<String>;
|
|||
*/
|
||||
class AsyncEventSourceMessage {
|
||||
|
||||
private:
|
||||
const AsyncEvent_SharedData_t _data;
|
||||
size_t _sent{0}; // num of bytes already sent
|
||||
size_t _acked{0}; // num of bytes acked
|
||||
private:
|
||||
const AsyncEvent_SharedData_t _data;
|
||||
size_t _sent{0}; // num of bytes already sent
|
||||
size_t _acked{0}; // num of bytes acked
|
||||
|
||||
public:
|
||||
AsyncEventSourceMessage(AsyncEvent_SharedData_t data) : _data(data) {};
|
||||
#ifdef ESP32
|
||||
AsyncEventSourceMessage(const char* data, size_t len) : _data(std::make_shared<String>(data, len)) {};
|
||||
public:
|
||||
AsyncEventSourceMessage(AsyncEvent_SharedData_t data) : _data(data){};
|
||||
#if defined(ESP32)
|
||||
AsyncEventSourceMessage(const char *data, size_t len) : _data(std::make_shared<String>(data, len)){};
|
||||
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
|
||||
AsyncEventSourceMessage(const char *data, size_t len) : _data(std::make_shared<String>()) {
|
||||
if (data && len > 0) {
|
||||
_data->concat(data, len);
|
||||
}
|
||||
};
|
||||
#else
|
||||
// esp8266's String does not have constructor with data/length arguments. Use a concat method here
|
||||
AsyncEventSourceMessage(const char* data, size_t len) { _data->concat(data, len); };
|
||||
// esp8266's String does not have constructor with data/length arguments. Use a concat method here
|
||||
AsyncEventSourceMessage(const char *data, size_t len) {
|
||||
_data->concat(data, len);
|
||||
};
|
||||
#endif
|
||||
|
||||
/**
|
||||
/**
|
||||
* @brief acknowledge sending len bytes of data
|
||||
* @note if num of bytes to ack is larger then the unacknowledged message length the number of carried over bytes are returned
|
||||
*
|
||||
* @param len bytes to acknowlegde
|
||||
* @param len bytes to acknowledge
|
||||
* @param time
|
||||
* @return size_t number of extra bytes carried over
|
||||
*/
|
||||
size_t ack(size_t len, uint32_t time = 0);
|
||||
size_t ack(size_t len, uint32_t time = 0);
|
||||
|
||||
/**
|
||||
/**
|
||||
* @brief write message data to client's buffer
|
||||
* @note this method does NOT call client's send
|
||||
*
|
||||
* @param client
|
||||
* @return size_t number of bytes written
|
||||
*/
|
||||
size_t write(AsyncClient* client);
|
||||
size_t write(AsyncClient *client);
|
||||
|
||||
/**
|
||||
/**
|
||||
* @brief writes message data to client's buffer and calls client's send method
|
||||
*
|
||||
* @param client
|
||||
* @return size_t returns num of bytes the clien was able to send()
|
||||
*/
|
||||
size_t send(AsyncClient* client);
|
||||
size_t send(AsyncClient *client);
|
||||
|
||||
// returns true if full message's length were acked
|
||||
bool finished() { return _acked == _data->length(); }
|
||||
// returns true if full message's length were acked
|
||||
bool finished() {
|
||||
return _acked == _data->length();
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* @brief returns true if all data has been sent already
|
||||
*
|
||||
*/
|
||||
bool sent() { return _sent == _data->length(); }
|
||||
bool sent() {
|
||||
return _sent == _data->length();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -125,25 +121,25 @@ class AsyncEventSourceMessage {
|
|||
*
|
||||
*/
|
||||
class AsyncEventSourceClient {
|
||||
private:
|
||||
AsyncClient* _client;
|
||||
AsyncEventSource* _server;
|
||||
uint32_t _lastId{0};
|
||||
size_t _inflight{0}; // num of unacknowledged bytes that has been written to socket buffer
|
||||
size_t _max_inflight{SSE_MAX_INFLIGH}; // max num of unacknowledged bytes that could be written to socket buffer
|
||||
std::list<AsyncEventSourceMessage> _messageQueue;
|
||||
private:
|
||||
AsyncClient *_client;
|
||||
AsyncEventSource *_server;
|
||||
uint32_t _lastId{0};
|
||||
size_t _inflight{0}; // num of unacknowledged bytes that has been written to socket buffer
|
||||
size_t _max_inflight{SSE_MAX_INFLIGH}; // max num of unacknowledged bytes that could be written to socket buffer
|
||||
std::list<AsyncEventSourceMessage> _messageQueue;
|
||||
#ifdef ESP32
|
||||
mutable std::mutex _lockmq;
|
||||
mutable std::mutex _lockmq;
|
||||
#endif
|
||||
bool _queueMessage(const char* message, size_t len);
|
||||
bool _queueMessage(AsyncEvent_SharedData_t&& msg);
|
||||
void _runQueue();
|
||||
bool _queueMessage(const char *message, size_t len);
|
||||
bool _queueMessage(AsyncEvent_SharedData_t &&msg);
|
||||
void _runQueue();
|
||||
|
||||
public:
|
||||
AsyncEventSourceClient(AsyncWebServerRequest* request, AsyncEventSource* server);
|
||||
~AsyncEventSourceClient();
|
||||
public:
|
||||
AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server);
|
||||
~AsyncEventSourceClient();
|
||||
|
||||
/**
|
||||
/**
|
||||
* @brief Send an SSE message to client
|
||||
* it will craft an SSE message and place it to client's message queue
|
||||
*
|
||||
|
@ -154,11 +150,15 @@ class AsyncEventSourceClient {
|
|||
* @return true if message was placed in a queue
|
||||
* @return false if queue is full
|
||||
*/
|
||||
bool send(const char* message, const char* event = NULL, uint32_t id = 0, uint32_t reconnect = 0);
|
||||
bool send(const String& message, const String& event, uint32_t id = 0, uint32_t reconnect = 0) { return send(message.c_str(), event.c_str(), id, reconnect); }
|
||||
bool send(const String& message, const char* event, uint32_t id = 0, uint32_t reconnect = 0) { return send(message.c_str(), event, id, reconnect); }
|
||||
bool send(const char *message, const char *event = NULL, uint32_t id = 0, uint32_t reconnect = 0);
|
||||
bool send(const String &message, const String &event, uint32_t id = 0, uint32_t reconnect = 0) {
|
||||
return send(message.c_str(), event.c_str(), id, reconnect);
|
||||
}
|
||||
bool send(const String &message, const char *event, uint32_t id = 0, uint32_t reconnect = 0) {
|
||||
return send(message.c_str(), event, id, reconnect);
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* @brief place supplied preformatted SSE message to the message queue
|
||||
* @note message must a properly formatted SSE string according to https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events
|
||||
*
|
||||
|
@ -166,42 +166,56 @@ class AsyncEventSourceClient {
|
|||
* @return true on success
|
||||
* @return false on queue overflow or no client connected
|
||||
*/
|
||||
bool write(AsyncEvent_SharedData_t message) { return connected() && _queueMessage(std::move(message)); };
|
||||
bool write(AsyncEvent_SharedData_t message) {
|
||||
return connected() && _queueMessage(std::move(message));
|
||||
};
|
||||
|
||||
[[deprecated("Use _write(AsyncEvent_SharedData_t message) instead to share same data with multiple SSE clients")]]
|
||||
bool write(const char* message, size_t len) { return connected() && _queueMessage(message, len); };
|
||||
[[deprecated("Use _write(AsyncEvent_SharedData_t message) instead to share same data with multiple SSE clients")]]
|
||||
bool write(const char *message, size_t len) {
|
||||
return connected() && _queueMessage(message, len);
|
||||
};
|
||||
|
||||
// close client's connection
|
||||
void close();
|
||||
// close client's connection
|
||||
void close();
|
||||
|
||||
// getters
|
||||
// getters
|
||||
|
||||
AsyncClient* client() { return _client; }
|
||||
bool connected() const { return _client && _client->connected(); }
|
||||
uint32_t lastId() const { return _lastId; }
|
||||
size_t packetsWaiting() const { return _messageQueue.size(); };
|
||||
AsyncClient *client() {
|
||||
return _client;
|
||||
}
|
||||
bool connected() const {
|
||||
return _client && _client->connected();
|
||||
}
|
||||
uint32_t lastId() const {
|
||||
return _lastId;
|
||||
}
|
||||
size_t packetsWaiting() const {
|
||||
return _messageQueue.size();
|
||||
};
|
||||
|
||||
/**
|
||||
/**
|
||||
* @brief Sets max amount of bytes that could be written to client's socket while awaiting delivery acknowledge
|
||||
* used to throttle message delivery length to tradeoff memory consumption
|
||||
* @note actual amount of data written could possible be a bit larger but no more than available socket buff space
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
void set_max_inflight_bytes(size_t value);
|
||||
void set_max_inflight_bytes(size_t value);
|
||||
|
||||
/**
|
||||
/**
|
||||
* @brief Get current max inflight bytes value
|
||||
*
|
||||
* @return size_t
|
||||
*/
|
||||
size_t get_max_inflight_bytes() const { return _max_inflight; }
|
||||
size_t get_max_inflight_bytes() const {
|
||||
return _max_inflight;
|
||||
}
|
||||
|
||||
// system callbacks (do not call if from user code!)
|
||||
void _onAck(size_t len, uint32_t time);
|
||||
void _onPoll();
|
||||
void _onTimeout(uint32_t time);
|
||||
void _onDisconnect();
|
||||
// system callbacks (do not call if from user code!)
|
||||
void _onAck(size_t len, uint32_t time);
|
||||
void _onPoll();
|
||||
void _onTimeout(uint32_t time);
|
||||
void _onDisconnect();
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -210,44 +224,50 @@ class AsyncEventSourceClient {
|
|||
*
|
||||
*/
|
||||
class AsyncEventSource : public AsyncWebHandler {
|
||||
private:
|
||||
String _url;
|
||||
std::list<std::unique_ptr<AsyncEventSourceClient>> _clients;
|
||||
private:
|
||||
String _url;
|
||||
std::list<std::unique_ptr<AsyncEventSourceClient>> _clients;
|
||||
#ifdef ESP32
|
||||
// Same as for individual messages, protect mutations of _clients list
|
||||
// since simultaneous access from different tasks is possible
|
||||
mutable std::mutex _client_queue_lock;
|
||||
// Same as for individual messages, protect mutations of _clients list
|
||||
// since simultaneous access from different tasks is possible
|
||||
mutable std::mutex _client_queue_lock;
|
||||
#endif
|
||||
ArEventHandlerFunction _connectcb = nullptr;
|
||||
ArEventHandlerFunction _disconnectcb = nullptr;
|
||||
ArEventHandlerFunction _connectcb = nullptr;
|
||||
ArEventHandlerFunction _disconnectcb = nullptr;
|
||||
|
||||
// this method manipulates in-fligh data size for connected client depending on number of active connections
|
||||
void _adjust_inflight_window();
|
||||
// this method manipulates in-fligh data size for connected client depending on number of active connections
|
||||
void _adjust_inflight_window();
|
||||
|
||||
public:
|
||||
typedef enum {
|
||||
DISCARDED = 0,
|
||||
ENQUEUED = 1,
|
||||
PARTIALLY_ENQUEUED = 2,
|
||||
} SendStatus;
|
||||
public:
|
||||
typedef enum {
|
||||
DISCARDED = 0,
|
||||
ENQUEUED = 1,
|
||||
PARTIALLY_ENQUEUED = 2,
|
||||
} SendStatus;
|
||||
|
||||
AsyncEventSource(const char* url) : _url(url) {};
|
||||
AsyncEventSource(const String& url) : _url(url) {};
|
||||
~AsyncEventSource() { close(); };
|
||||
AsyncEventSource(const char *url) : _url(url){};
|
||||
AsyncEventSource(const String &url) : _url(url){};
|
||||
~AsyncEventSource() {
|
||||
close();
|
||||
};
|
||||
|
||||
const char* url() const { return _url.c_str(); }
|
||||
// close all connected clients
|
||||
void close();
|
||||
const char *url() const {
|
||||
return _url.c_str();
|
||||
}
|
||||
// close all connected clients
|
||||
void close();
|
||||
|
||||
/**
|
||||
/**
|
||||
* @brief set on-connect callback for the client
|
||||
* used to deliver messages to client on first connect
|
||||
*
|
||||
* @param cb
|
||||
*/
|
||||
void onConnect(ArEventHandlerFunction cb) { _connectcb = cb; }
|
||||
void onConnect(ArEventHandlerFunction cb) {
|
||||
_connectcb = cb;
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* @brief Send an SSE message to client
|
||||
* it will craft an SSE message and place it to all connected client's message queues
|
||||
*
|
||||
|
@ -257,36 +277,44 @@ class AsyncEventSource : public AsyncWebHandler {
|
|||
* @param reconnect client's reconnect timeout
|
||||
* @return SendStatus if message was placed in any/all/part of the client's queues
|
||||
*/
|
||||
SendStatus send(const char* message, const char* event = NULL, uint32_t id = 0, uint32_t reconnect = 0);
|
||||
SendStatus send(const String& message, const String& event, uint32_t id = 0, uint32_t reconnect = 0) { return send(message.c_str(), event.c_str(), id, reconnect); }
|
||||
SendStatus send(const String& message, const char* event, uint32_t id = 0, uint32_t reconnect = 0) { return send(message.c_str(), event, id, reconnect); }
|
||||
SendStatus send(const char *message, const char *event = NULL, uint32_t id = 0, uint32_t reconnect = 0);
|
||||
SendStatus send(const String &message, const String &event, uint32_t id = 0, uint32_t reconnect = 0) {
|
||||
return send(message.c_str(), event.c_str(), id, reconnect);
|
||||
}
|
||||
SendStatus send(const String &message, const char *event, uint32_t id = 0, uint32_t reconnect = 0) {
|
||||
return send(message.c_str(), event, id, reconnect);
|
||||
}
|
||||
|
||||
// The client pointer sent to the callback is only for reference purposes. DO NOT CALL ANY METHOD ON IT !
|
||||
void onDisconnect(ArEventHandlerFunction cb) { _disconnectcb = cb; }
|
||||
void authorizeConnect(ArAuthorizeConnectHandler cb);
|
||||
// The client pointer sent to the callback is only for reference purposes. DO NOT CALL ANY METHOD ON IT !
|
||||
void onDisconnect(ArEventHandlerFunction cb) {
|
||||
_disconnectcb = cb;
|
||||
}
|
||||
void authorizeConnect(ArAuthorizeConnectHandler cb);
|
||||
|
||||
// returns number of connected clients
|
||||
size_t count() const;
|
||||
// returns number of connected clients
|
||||
size_t count() const;
|
||||
|
||||
// returns average number of messages pending in all client's queues
|
||||
size_t avgPacketsWaiting() const;
|
||||
// returns average number of messages pending in all client's queues
|
||||
size_t avgPacketsWaiting() const;
|
||||
|
||||
// system callbacks (do not call from user code!)
|
||||
void _addClient(AsyncEventSourceClient* client);
|
||||
void _handleDisconnect(AsyncEventSourceClient* client);
|
||||
bool canHandle(AsyncWebServerRequest* request) const override final;
|
||||
void handleRequest(AsyncWebServerRequest* request) override final;
|
||||
// system callbacks (do not call from user code!)
|
||||
void _addClient(AsyncEventSourceClient *client);
|
||||
void _handleDisconnect(AsyncEventSourceClient *client);
|
||||
bool canHandle(AsyncWebServerRequest *request) const override final;
|
||||
void handleRequest(AsyncWebServerRequest *request) override final;
|
||||
};
|
||||
|
||||
class AsyncEventSourceResponse : public AsyncWebServerResponse {
|
||||
private:
|
||||
AsyncEventSource* _server;
|
||||
private:
|
||||
AsyncEventSource *_server;
|
||||
|
||||
public:
|
||||
AsyncEventSourceResponse(AsyncEventSource* server);
|
||||
void _respond(AsyncWebServerRequest* request);
|
||||
size_t _ack(AsyncWebServerRequest* request, size_t len, uint32_t time);
|
||||
bool _sourceValid() const { return true; }
|
||||
public:
|
||||
AsyncEventSourceResponse(AsyncEventSource *server);
|
||||
void _respond(AsyncWebServerRequest *request);
|
||||
size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time);
|
||||
bool _sourceValid() const {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* ASYNCEVENTSOURCE_H_ */
|
||||
|
|
|
@ -1,108 +1,117 @@
|
|||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
|
||||
|
||||
#include "AsyncJson.h"
|
||||
|
||||
#if ASYNC_JSON_SUPPORT == 1
|
||||
|
||||
#if ARDUINOJSON_VERSION_MAJOR == 5
|
||||
#if ARDUINOJSON_VERSION_MAJOR == 5
|
||||
AsyncJsonResponse::AsyncJsonResponse(bool isArray) : _isValid{false} {
|
||||
_code = 200;
|
||||
_contentType = asyncsrv::T_application_json;
|
||||
if (isArray)
|
||||
if (isArray) {
|
||||
_root = _jsonBuffer.createArray();
|
||||
else
|
||||
} else {
|
||||
_root = _jsonBuffer.createObject();
|
||||
}
|
||||
}
|
||||
#elif ARDUINOJSON_VERSION_MAJOR == 6
|
||||
#elif ARDUINOJSON_VERSION_MAJOR == 6
|
||||
AsyncJsonResponse::AsyncJsonResponse(bool isArray, size_t maxJsonBufferSize) : _jsonBuffer(maxJsonBufferSize), _isValid{false} {
|
||||
_code = 200;
|
||||
_contentType = asyncsrv::T_application_json;
|
||||
if (isArray)
|
||||
if (isArray) {
|
||||
_root = _jsonBuffer.createNestedArray();
|
||||
else
|
||||
} else {
|
||||
_root = _jsonBuffer.createNestedObject();
|
||||
}
|
||||
}
|
||||
#else
|
||||
#else
|
||||
AsyncJsonResponse::AsyncJsonResponse(bool isArray) : _isValid{false} {
|
||||
_code = 200;
|
||||
_contentType = asyncsrv::T_application_json;
|
||||
if (isArray)
|
||||
if (isArray) {
|
||||
_root = _jsonBuffer.add<JsonArray>();
|
||||
else
|
||||
} else {
|
||||
_root = _jsonBuffer.add<JsonObject>();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
size_t AsyncJsonResponse::setLength() {
|
||||
#if ARDUINOJSON_VERSION_MAJOR == 5
|
||||
#if ARDUINOJSON_VERSION_MAJOR == 5
|
||||
_contentLength = _root.measureLength();
|
||||
#else
|
||||
#else
|
||||
_contentLength = measureJson(_root);
|
||||
#endif
|
||||
#endif
|
||||
if (_contentLength) {
|
||||
_isValid = true;
|
||||
}
|
||||
return _contentLength;
|
||||
}
|
||||
|
||||
size_t AsyncJsonResponse::_fillBuffer(uint8_t* data, size_t len) {
|
||||
size_t AsyncJsonResponse::_fillBuffer(uint8_t *data, size_t len) {
|
||||
ChunkPrint dest(data, _sentLength, len);
|
||||
#if ARDUINOJSON_VERSION_MAJOR == 5
|
||||
#if ARDUINOJSON_VERSION_MAJOR == 5
|
||||
_root.printTo(dest);
|
||||
#else
|
||||
#else
|
||||
serializeJson(_root, dest);
|
||||
#endif
|
||||
#endif
|
||||
return len;
|
||||
}
|
||||
|
||||
#if ARDUINOJSON_VERSION_MAJOR == 6
|
||||
#if ARDUINOJSON_VERSION_MAJOR == 6
|
||||
PrettyAsyncJsonResponse::PrettyAsyncJsonResponse(bool isArray, size_t maxJsonBufferSize) : AsyncJsonResponse{isArray, maxJsonBufferSize} {}
|
||||
#else
|
||||
#else
|
||||
PrettyAsyncJsonResponse::PrettyAsyncJsonResponse(bool isArray) : AsyncJsonResponse{isArray} {}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
size_t PrettyAsyncJsonResponse::setLength() {
|
||||
#if ARDUINOJSON_VERSION_MAJOR == 5
|
||||
#if ARDUINOJSON_VERSION_MAJOR == 5
|
||||
_contentLength = _root.measurePrettyLength();
|
||||
#else
|
||||
#else
|
||||
_contentLength = measureJsonPretty(_root);
|
||||
#endif
|
||||
#endif
|
||||
if (_contentLength) {
|
||||
_isValid = true;
|
||||
}
|
||||
return _contentLength;
|
||||
}
|
||||
|
||||
size_t PrettyAsyncJsonResponse::_fillBuffer(uint8_t* data, size_t len) {
|
||||
size_t PrettyAsyncJsonResponse::_fillBuffer(uint8_t *data, size_t len) {
|
||||
ChunkPrint dest(data, _sentLength, len);
|
||||
#if ARDUINOJSON_VERSION_MAJOR == 5
|
||||
#if ARDUINOJSON_VERSION_MAJOR == 5
|
||||
_root.prettyPrintTo(dest);
|
||||
#else
|
||||
#else
|
||||
serializeJsonPretty(_root, dest);
|
||||
#endif
|
||||
#endif
|
||||
return len;
|
||||
}
|
||||
|
||||
#if ARDUINOJSON_VERSION_MAJOR == 6
|
||||
AsyncCallbackJsonWebHandler::AsyncCallbackJsonWebHandler(const String& uri, ArJsonRequestHandlerFunction onRequest, size_t maxJsonBufferSize)
|
||||
: _uri(uri), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), maxJsonBufferSize(maxJsonBufferSize), _maxContentLength(16384) {}
|
||||
#else
|
||||
AsyncCallbackJsonWebHandler::AsyncCallbackJsonWebHandler(const String& uri, ArJsonRequestHandlerFunction onRequest)
|
||||
: _uri(uri), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), _maxContentLength(16384) {}
|
||||
#endif
|
||||
#if ARDUINOJSON_VERSION_MAJOR == 6
|
||||
AsyncCallbackJsonWebHandler::AsyncCallbackJsonWebHandler(const String &uri, ArJsonRequestHandlerFunction onRequest, size_t maxJsonBufferSize)
|
||||
: _uri(uri), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), maxJsonBufferSize(maxJsonBufferSize), _maxContentLength(16384) {}
|
||||
#else
|
||||
AsyncCallbackJsonWebHandler::AsyncCallbackJsonWebHandler(const String &uri, ArJsonRequestHandlerFunction onRequest)
|
||||
: _uri(uri), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), _maxContentLength(16384) {}
|
||||
#endif
|
||||
|
||||
bool AsyncCallbackJsonWebHandler::canHandle(AsyncWebServerRequest* request) const {
|
||||
if (!_onRequest || !request->isHTTP() || !(_method & request->method()))
|
||||
bool AsyncCallbackJsonWebHandler::canHandle(AsyncWebServerRequest *request) const {
|
||||
if (!_onRequest || !request->isHTTP() || !(_method & request->method())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri + "/")))
|
||||
if (_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri + "/"))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (request->method() != HTTP_GET && !request->contentType().equalsIgnoreCase(asyncsrv::T_application_json))
|
||||
if (request->method() != HTTP_GET && !request->contentType().equalsIgnoreCase(asyncsrv::T_application_json)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void AsyncCallbackJsonWebHandler::handleRequest(AsyncWebServerRequest* request) {
|
||||
void AsyncCallbackJsonWebHandler::handleRequest(AsyncWebServerRequest *request) {
|
||||
if (_onRequest) {
|
||||
if (request->method() == HTTP_GET) {
|
||||
JsonVariant json;
|
||||
|
@ -110,21 +119,21 @@ void AsyncCallbackJsonWebHandler::handleRequest(AsyncWebServerRequest* request)
|
|||
return;
|
||||
} else if (request->_tempObject != NULL) {
|
||||
|
||||
#if ARDUINOJSON_VERSION_MAJOR == 5
|
||||
#if ARDUINOJSON_VERSION_MAJOR == 5
|
||||
DynamicJsonBuffer jsonBuffer;
|
||||
JsonVariant json = jsonBuffer.parse((uint8_t*)(request->_tempObject));
|
||||
JsonVariant json = jsonBuffer.parse((uint8_t *)(request->_tempObject));
|
||||
if (json.success()) {
|
||||
#elif ARDUINOJSON_VERSION_MAJOR == 6
|
||||
#elif ARDUINOJSON_VERSION_MAJOR == 6
|
||||
DynamicJsonDocument jsonBuffer(this->maxJsonBufferSize);
|
||||
DeserializationError error = deserializeJson(jsonBuffer, (uint8_t*)(request->_tempObject));
|
||||
DeserializationError error = deserializeJson(jsonBuffer, (uint8_t *)(request->_tempObject));
|
||||
if (!error) {
|
||||
JsonVariant json = jsonBuffer.as<JsonVariant>();
|
||||
#else
|
||||
#else
|
||||
JsonDocument jsonBuffer;
|
||||
DeserializationError error = deserializeJson(jsonBuffer, (uint8_t*)(request->_tempObject));
|
||||
DeserializationError error = deserializeJson(jsonBuffer, (uint8_t *)(request->_tempObject));
|
||||
if (!error) {
|
||||
JsonVariant json = jsonBuffer.as<JsonVariant>();
|
||||
#endif
|
||||
#endif
|
||||
|
||||
_onRequest(request, json);
|
||||
return;
|
||||
|
@ -136,16 +145,23 @@ void AsyncCallbackJsonWebHandler::handleRequest(AsyncWebServerRequest* request)
|
|||
}
|
||||
}
|
||||
|
||||
void AsyncCallbackJsonWebHandler::handleBody(AsyncWebServerRequest* request, uint8_t* data, size_t len, size_t index, size_t total) {
|
||||
void AsyncCallbackJsonWebHandler::handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
|
||||
if (_onRequest) {
|
||||
_contentLength = total;
|
||||
if (total > 0 && request->_tempObject == NULL && total < _maxContentLength) {
|
||||
request->_tempObject = malloc(total);
|
||||
if (request->_tempObject == NULL) {
|
||||
#ifdef ESP32
|
||||
log_e("Failed to allocate");
|
||||
#endif
|
||||
request->abort();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (request->_tempObject != NULL) {
|
||||
memcpy((uint8_t*)(request->_tempObject) + index, data, len);
|
||||
memcpy((uint8_t *)(request->_tempObject) + index, data, len);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // ASYNC_JSON_SUPPORT
|
||||
#endif // ASYNC_JSON_SUPPORT
|
||||
|
|
|
@ -1,131 +1,119 @@
|
|||
// AsyncJson.h
|
||||
/*
|
||||
Async Response to use with ArduinoJson and AsyncWebServer
|
||||
Written by Andrew Melvin (SticilFace) with help from me-no-dev and BBlanchon.
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
|
||||
|
||||
Example of callback in use
|
||||
|
||||
server.on("/json", HTTP_ANY, [](AsyncWebServerRequest * request) {
|
||||
|
||||
AsyncJsonResponse * response = new AsyncJsonResponse();
|
||||
JsonObject& root = response->getRoot();
|
||||
root["key1"] = "key number one";
|
||||
JsonObject& nested = root.createNestedObject("nested");
|
||||
nested["key1"] = "key number one";
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
});
|
||||
|
||||
--------------------
|
||||
|
||||
Async Request to use with ArduinoJson and AsyncWebServer
|
||||
Written by Arsène von Wyss (avonwyss)
|
||||
|
||||
Example
|
||||
|
||||
AsyncCallbackJsonWebHandler* handler = new AsyncCallbackJsonWebHandler("/rest/endpoint");
|
||||
handler->onRequest([](AsyncWebServerRequest *request, JsonVariant &json) {
|
||||
JsonObject jsonObj = json.as<JsonObject>();
|
||||
// ...
|
||||
});
|
||||
server.addHandler(handler);
|
||||
|
||||
*/
|
||||
#ifndef ASYNC_JSON_H_
|
||||
#define ASYNC_JSON_H_
|
||||
|
||||
#if __has_include("ArduinoJson.h")
|
||||
#include <ArduinoJson.h>
|
||||
#if ARDUINOJSON_VERSION_MAJOR >= 5
|
||||
#define ASYNC_JSON_SUPPORT 1
|
||||
#else
|
||||
#define ASYNC_JSON_SUPPORT 0
|
||||
#endif // ARDUINOJSON_VERSION_MAJOR >= 5
|
||||
#endif // __has_include("ArduinoJson.h")
|
||||
#include <ArduinoJson.h>
|
||||
#if ARDUINOJSON_VERSION_MAJOR >= 5
|
||||
#define ASYNC_JSON_SUPPORT 1
|
||||
#else
|
||||
#define ASYNC_JSON_SUPPORT 0
|
||||
#endif // ARDUINOJSON_VERSION_MAJOR >= 5
|
||||
#endif // __has_include("ArduinoJson.h")
|
||||
|
||||
#if ASYNC_JSON_SUPPORT == 1
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
|
||||
#include "ChunkPrint.h"
|
||||
#include "ChunkPrint.h"
|
||||
|
||||
#if ARDUINOJSON_VERSION_MAJOR == 6
|
||||
#ifndef DYNAMIC_JSON_DOCUMENT_SIZE
|
||||
#define DYNAMIC_JSON_DOCUMENT_SIZE 1024
|
||||
#endif
|
||||
#endif
|
||||
#if ARDUINOJSON_VERSION_MAJOR == 6
|
||||
#ifndef DYNAMIC_JSON_DOCUMENT_SIZE
|
||||
#define DYNAMIC_JSON_DOCUMENT_SIZE 1024
|
||||
#endif
|
||||
#endif
|
||||
|
||||
class AsyncJsonResponse : public AsyncAbstractResponse {
|
||||
protected:
|
||||
#if ARDUINOJSON_VERSION_MAJOR == 5
|
||||
DynamicJsonBuffer _jsonBuffer;
|
||||
#elif ARDUINOJSON_VERSION_MAJOR == 6
|
||||
DynamicJsonDocument _jsonBuffer;
|
||||
#else
|
||||
JsonDocument _jsonBuffer;
|
||||
#endif
|
||||
protected:
|
||||
#if ARDUINOJSON_VERSION_MAJOR == 5
|
||||
DynamicJsonBuffer _jsonBuffer;
|
||||
#elif ARDUINOJSON_VERSION_MAJOR == 6
|
||||
DynamicJsonDocument _jsonBuffer;
|
||||
#else
|
||||
JsonDocument _jsonBuffer;
|
||||
#endif
|
||||
|
||||
JsonVariant _root;
|
||||
bool _isValid;
|
||||
JsonVariant _root;
|
||||
bool _isValid;
|
||||
|
||||
public:
|
||||
#if ARDUINOJSON_VERSION_MAJOR == 6
|
||||
AsyncJsonResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE);
|
||||
#else
|
||||
AsyncJsonResponse(bool isArray = false);
|
||||
#endif
|
||||
JsonVariant& getRoot() { return _root; }
|
||||
bool _sourceValid() const { return _isValid; }
|
||||
size_t setLength();
|
||||
size_t getSize() const { return _jsonBuffer.size(); }
|
||||
size_t _fillBuffer(uint8_t* data, size_t len);
|
||||
#if ARDUINOJSON_VERSION_MAJOR >= 6
|
||||
bool overflowed() const { return _jsonBuffer.overflowed(); }
|
||||
#endif
|
||||
public:
|
||||
#if ARDUINOJSON_VERSION_MAJOR == 6
|
||||
AsyncJsonResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE);
|
||||
#else
|
||||
AsyncJsonResponse(bool isArray = false);
|
||||
#endif
|
||||
JsonVariant &getRoot() {
|
||||
return _root;
|
||||
}
|
||||
bool _sourceValid() const {
|
||||
return _isValid;
|
||||
}
|
||||
size_t setLength();
|
||||
size_t getSize() const {
|
||||
return _jsonBuffer.size();
|
||||
}
|
||||
size_t _fillBuffer(uint8_t *data, size_t len);
|
||||
#if ARDUINOJSON_VERSION_MAJOR >= 6
|
||||
bool overflowed() const {
|
||||
return _jsonBuffer.overflowed();
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
class PrettyAsyncJsonResponse : public AsyncJsonResponse {
|
||||
public:
|
||||
#if ARDUINOJSON_VERSION_MAJOR == 6
|
||||
PrettyAsyncJsonResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE);
|
||||
#else
|
||||
PrettyAsyncJsonResponse(bool isArray = false);
|
||||
#endif
|
||||
size_t setLength();
|
||||
size_t _fillBuffer(uint8_t* data, size_t len);
|
||||
public:
|
||||
#if ARDUINOJSON_VERSION_MAJOR == 6
|
||||
PrettyAsyncJsonResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE);
|
||||
#else
|
||||
PrettyAsyncJsonResponse(bool isArray = false);
|
||||
#endif
|
||||
size_t setLength();
|
||||
size_t _fillBuffer(uint8_t *data, size_t len);
|
||||
};
|
||||
|
||||
typedef std::function<void(AsyncWebServerRequest* request, JsonVariant& json)> ArJsonRequestHandlerFunction;
|
||||
typedef std::function<void(AsyncWebServerRequest *request, JsonVariant &json)> ArJsonRequestHandlerFunction;
|
||||
|
||||
class AsyncCallbackJsonWebHandler : public AsyncWebHandler {
|
||||
protected:
|
||||
String _uri;
|
||||
WebRequestMethodComposite _method;
|
||||
ArJsonRequestHandlerFunction _onRequest;
|
||||
size_t _contentLength;
|
||||
#if ARDUINOJSON_VERSION_MAJOR == 6
|
||||
size_t maxJsonBufferSize;
|
||||
#endif
|
||||
size_t _maxContentLength;
|
||||
protected:
|
||||
String _uri;
|
||||
WebRequestMethodComposite _method;
|
||||
ArJsonRequestHandlerFunction _onRequest;
|
||||
size_t _contentLength;
|
||||
#if ARDUINOJSON_VERSION_MAJOR == 6
|
||||
size_t maxJsonBufferSize;
|
||||
#endif
|
||||
size_t _maxContentLength;
|
||||
|
||||
public:
|
||||
#if ARDUINOJSON_VERSION_MAJOR == 6
|
||||
AsyncCallbackJsonWebHandler(const String& uri, ArJsonRequestHandlerFunction onRequest = nullptr, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE);
|
||||
#else
|
||||
AsyncCallbackJsonWebHandler(const String& uri, ArJsonRequestHandlerFunction onRequest = nullptr);
|
||||
#endif
|
||||
public:
|
||||
#if ARDUINOJSON_VERSION_MAJOR == 6
|
||||
AsyncCallbackJsonWebHandler(const String &uri, ArJsonRequestHandlerFunction onRequest = nullptr, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE);
|
||||
#else
|
||||
AsyncCallbackJsonWebHandler(const String &uri, ArJsonRequestHandlerFunction onRequest = nullptr);
|
||||
#endif
|
||||
|
||||
void setMethod(WebRequestMethodComposite method) { _method = method; }
|
||||
void setMaxContentLength(int maxContentLength) { _maxContentLength = maxContentLength; }
|
||||
void onRequest(ArJsonRequestHandlerFunction fn) { _onRequest = fn; }
|
||||
void setMethod(WebRequestMethodComposite method) {
|
||||
_method = method;
|
||||
}
|
||||
void setMaxContentLength(int maxContentLength) {
|
||||
_maxContentLength = maxContentLength;
|
||||
}
|
||||
void onRequest(ArJsonRequestHandlerFunction fn) {
|
||||
_onRequest = fn;
|
||||
}
|
||||
|
||||
bool canHandle(AsyncWebServerRequest* request) const override final;
|
||||
void handleRequest(AsyncWebServerRequest* request) override final;
|
||||
void handleUpload(__unused AsyncWebServerRequest* request, __unused const String& filename, __unused size_t index, __unused uint8_t* data, __unused size_t len, __unused bool final) override final {}
|
||||
void handleBody(AsyncWebServerRequest* request, uint8_t* data, size_t len, size_t index, size_t total) override final;
|
||||
bool isRequestHandlerTrivial() const override final { return !_onRequest; }
|
||||
bool canHandle(AsyncWebServerRequest *request) const override final;
|
||||
void handleRequest(AsyncWebServerRequest *request) override final;
|
||||
void handleUpload(
|
||||
__unused AsyncWebServerRequest *request, __unused const String &filename, __unused size_t index, __unused uint8_t *data, __unused size_t len,
|
||||
__unused bool final
|
||||
) override final {}
|
||||
void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override final;
|
||||
bool isRequestHandlerTrivial() const override final {
|
||||
return !_onRequest;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // ASYNC_JSON_SUPPORT == 1
|
||||
#endif // ASYNC_JSON_SUPPORT == 1
|
||||
|
||||
#endif // ASYNC_JSON_H_
|
||||
#endif // ASYNC_JSON_H_
|
||||
|
|
|
@ -1,26 +1,31 @@
|
|||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
|
||||
|
||||
#include "AsyncMessagePack.h"
|
||||
|
||||
#if ASYNC_MSG_PACK_SUPPORT == 1
|
||||
|
||||
#if ARDUINOJSON_VERSION_MAJOR == 6
|
||||
#if ARDUINOJSON_VERSION_MAJOR == 6
|
||||
AsyncMessagePackResponse::AsyncMessagePackResponse(bool isArray, size_t maxJsonBufferSize) : _jsonBuffer(maxJsonBufferSize), _isValid{false} {
|
||||
_code = 200;
|
||||
_contentType = asyncsrv::T_application_msgpack;
|
||||
if (isArray)
|
||||
if (isArray) {
|
||||
_root = _jsonBuffer.createNestedArray();
|
||||
else
|
||||
} else {
|
||||
_root = _jsonBuffer.createNestedObject();
|
||||
}
|
||||
}
|
||||
#else
|
||||
#else
|
||||
AsyncMessagePackResponse::AsyncMessagePackResponse(bool isArray) : _isValid{false} {
|
||||
_code = 200;
|
||||
_contentType = asyncsrv::T_application_msgpack;
|
||||
if (isArray)
|
||||
if (isArray) {
|
||||
_root = _jsonBuffer.add<JsonArray>();
|
||||
else
|
||||
} else {
|
||||
_root = _jsonBuffer.add<JsonObject>();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
size_t AsyncMessagePackResponse::setLength() {
|
||||
_contentLength = measureMsgPack(_root);
|
||||
|
@ -30,34 +35,39 @@ size_t AsyncMessagePackResponse::setLength() {
|
|||
return _contentLength;
|
||||
}
|
||||
|
||||
size_t AsyncMessagePackResponse::_fillBuffer(uint8_t* data, size_t len) {
|
||||
size_t AsyncMessagePackResponse::_fillBuffer(uint8_t *data, size_t len) {
|
||||
ChunkPrint dest(data, _sentLength, len);
|
||||
serializeMsgPack(_root, dest);
|
||||
return len;
|
||||
}
|
||||
|
||||
#if ARDUINOJSON_VERSION_MAJOR == 6
|
||||
AsyncCallbackMessagePackWebHandler::AsyncCallbackMessagePackWebHandler(const String& uri, ArMessagePackRequestHandlerFunction onRequest, size_t maxJsonBufferSize)
|
||||
: _uri(uri), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), maxJsonBufferSize(maxJsonBufferSize), _maxContentLength(16384) {}
|
||||
#else
|
||||
AsyncCallbackMessagePackWebHandler::AsyncCallbackMessagePackWebHandler(const String& uri, ArMessagePackRequestHandlerFunction onRequest)
|
||||
: _uri(uri), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), _maxContentLength(16384) {}
|
||||
#endif
|
||||
#if ARDUINOJSON_VERSION_MAJOR == 6
|
||||
AsyncCallbackMessagePackWebHandler::AsyncCallbackMessagePackWebHandler(
|
||||
const String &uri, ArMessagePackRequestHandlerFunction onRequest, size_t maxJsonBufferSize
|
||||
)
|
||||
: _uri(uri), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), maxJsonBufferSize(maxJsonBufferSize), _maxContentLength(16384) {}
|
||||
#else
|
||||
AsyncCallbackMessagePackWebHandler::AsyncCallbackMessagePackWebHandler(const String &uri, ArMessagePackRequestHandlerFunction onRequest)
|
||||
: _uri(uri), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), _maxContentLength(16384) {}
|
||||
#endif
|
||||
|
||||
bool AsyncCallbackMessagePackWebHandler::canHandle(AsyncWebServerRequest* request) const {
|
||||
if (!_onRequest || !request->isHTTP() || !(_method & request->method()))
|
||||
bool AsyncCallbackMessagePackWebHandler::canHandle(AsyncWebServerRequest *request) const {
|
||||
if (!_onRequest || !request->isHTTP() || !(_method & request->method())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri + "/")))
|
||||
if (_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri + "/"))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (request->method() != HTTP_GET && !request->contentType().equalsIgnoreCase(asyncsrv::T_application_msgpack))
|
||||
if (request->method() != HTTP_GET && !request->contentType().equalsIgnoreCase(asyncsrv::T_application_msgpack)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void AsyncCallbackMessagePackWebHandler::handleRequest(AsyncWebServerRequest* request) {
|
||||
void AsyncCallbackMessagePackWebHandler::handleRequest(AsyncWebServerRequest *request) {
|
||||
if (_onRequest) {
|
||||
if (request->method() == HTTP_GET) {
|
||||
JsonVariant json;
|
||||
|
@ -65,17 +75,17 @@ void AsyncCallbackMessagePackWebHandler::handleRequest(AsyncWebServerRequest* re
|
|||
return;
|
||||
} else if (request->_tempObject != NULL) {
|
||||
|
||||
#if ARDUINOJSON_VERSION_MAJOR == 6
|
||||
#if ARDUINOJSON_VERSION_MAJOR == 6
|
||||
DynamicJsonDocument jsonBuffer(this->maxJsonBufferSize);
|
||||
DeserializationError error = deserializeMsgPack(jsonBuffer, (uint8_t*)(request->_tempObject));
|
||||
DeserializationError error = deserializeMsgPack(jsonBuffer, (uint8_t *)(request->_tempObject));
|
||||
if (!error) {
|
||||
JsonVariant json = jsonBuffer.as<JsonVariant>();
|
||||
#else
|
||||
#else
|
||||
JsonDocument jsonBuffer;
|
||||
DeserializationError error = deserializeMsgPack(jsonBuffer, (uint8_t*)(request->_tempObject));
|
||||
DeserializationError error = deserializeMsgPack(jsonBuffer, (uint8_t *)(request->_tempObject));
|
||||
if (!error) {
|
||||
JsonVariant json = jsonBuffer.as<JsonVariant>();
|
||||
#endif
|
||||
#endif
|
||||
|
||||
_onRequest(request, json);
|
||||
return;
|
||||
|
@ -87,16 +97,23 @@ void AsyncCallbackMessagePackWebHandler::handleRequest(AsyncWebServerRequest* re
|
|||
}
|
||||
}
|
||||
|
||||
void AsyncCallbackMessagePackWebHandler::handleBody(AsyncWebServerRequest* request, uint8_t* data, size_t len, size_t index, size_t total) {
|
||||
void AsyncCallbackMessagePackWebHandler::handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
|
||||
if (_onRequest) {
|
||||
_contentLength = total;
|
||||
if (total > 0 && request->_tempObject == NULL && total < _maxContentLength) {
|
||||
request->_tempObject = malloc(total);
|
||||
if (request->_tempObject == NULL) {
|
||||
#ifdef ESP32
|
||||
log_e("Failed to allocate");
|
||||
#endif
|
||||
request->abort();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (request->_tempObject != NULL) {
|
||||
memcpy((uint8_t*)(request->_tempObject) + index, data, len);
|
||||
memcpy((uint8_t *)(request->_tempObject) + index, data, len);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // ASYNC_MSG_PACK_SUPPORT
|
||||
#endif // ASYNC_MSG_PACK_SUPPORT
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
|
||||
|
||||
#pragma once
|
||||
|
||||
/*
|
||||
|
@ -22,81 +25,102 @@
|
|||
*/
|
||||
|
||||
#if __has_include("ArduinoJson.h")
|
||||
#include <ArduinoJson.h>
|
||||
#if ARDUINOJSON_VERSION_MAJOR >= 6
|
||||
#define ASYNC_MSG_PACK_SUPPORT 1
|
||||
#else
|
||||
#define ASYNC_MSG_PACK_SUPPORT 0
|
||||
#endif // ARDUINOJSON_VERSION_MAJOR >= 6
|
||||
#endif // __has_include("ArduinoJson.h")
|
||||
#include <ArduinoJson.h>
|
||||
#if ARDUINOJSON_VERSION_MAJOR >= 6
|
||||
#define ASYNC_MSG_PACK_SUPPORT 1
|
||||
#else
|
||||
#define ASYNC_MSG_PACK_SUPPORT 0
|
||||
#endif // ARDUINOJSON_VERSION_MAJOR >= 6
|
||||
#endif // __has_include("ArduinoJson.h")
|
||||
|
||||
#if ASYNC_MSG_PACK_SUPPORT == 1
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
|
||||
#include "ChunkPrint.h"
|
||||
#include "ChunkPrint.h"
|
||||
|
||||
#if ARDUINOJSON_VERSION_MAJOR == 6
|
||||
#ifndef DYNAMIC_JSON_DOCUMENT_SIZE
|
||||
#define DYNAMIC_JSON_DOCUMENT_SIZE 1024
|
||||
#endif
|
||||
#endif
|
||||
#if ARDUINOJSON_VERSION_MAJOR == 6
|
||||
#ifndef DYNAMIC_JSON_DOCUMENT_SIZE
|
||||
#define DYNAMIC_JSON_DOCUMENT_SIZE 1024
|
||||
#endif
|
||||
#endif
|
||||
|
||||
class AsyncMessagePackResponse : public AsyncAbstractResponse {
|
||||
protected:
|
||||
#if ARDUINOJSON_VERSION_MAJOR == 6
|
||||
DynamicJsonDocument _jsonBuffer;
|
||||
#else
|
||||
JsonDocument _jsonBuffer;
|
||||
#endif
|
||||
protected:
|
||||
#if ARDUINOJSON_VERSION_MAJOR == 6
|
||||
DynamicJsonDocument _jsonBuffer;
|
||||
#else
|
||||
JsonDocument _jsonBuffer;
|
||||
#endif
|
||||
|
||||
JsonVariant _root;
|
||||
bool _isValid;
|
||||
JsonVariant _root;
|
||||
bool _isValid;
|
||||
|
||||
public:
|
||||
#if ARDUINOJSON_VERSION_MAJOR == 6
|
||||
AsyncMessagePackResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE);
|
||||
#else
|
||||
AsyncMessagePackResponse(bool isArray = false);
|
||||
#endif
|
||||
JsonVariant& getRoot() { return _root; }
|
||||
bool _sourceValid() const { return _isValid; }
|
||||
size_t setLength();
|
||||
size_t getSize() const { return _jsonBuffer.size(); }
|
||||
size_t _fillBuffer(uint8_t* data, size_t len);
|
||||
#if ARDUINOJSON_VERSION_MAJOR >= 6
|
||||
bool overflowed() const { return _jsonBuffer.overflowed(); }
|
||||
#endif
|
||||
public:
|
||||
#if ARDUINOJSON_VERSION_MAJOR == 6
|
||||
AsyncMessagePackResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE);
|
||||
#else
|
||||
AsyncMessagePackResponse(bool isArray = false);
|
||||
#endif
|
||||
JsonVariant &getRoot() {
|
||||
return _root;
|
||||
}
|
||||
bool _sourceValid() const {
|
||||
return _isValid;
|
||||
}
|
||||
size_t setLength();
|
||||
size_t getSize() const {
|
||||
return _jsonBuffer.size();
|
||||
}
|
||||
size_t _fillBuffer(uint8_t *data, size_t len);
|
||||
#if ARDUINOJSON_VERSION_MAJOR >= 6
|
||||
bool overflowed() const {
|
||||
return _jsonBuffer.overflowed();
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
typedef std::function<void(AsyncWebServerRequest* request, JsonVariant& json)> ArMessagePackRequestHandlerFunction;
|
||||
typedef std::function<void(AsyncWebServerRequest *request, JsonVariant &json)> ArMessagePackRequestHandlerFunction;
|
||||
|
||||
class AsyncCallbackMessagePackWebHandler : public AsyncWebHandler {
|
||||
protected:
|
||||
String _uri;
|
||||
WebRequestMethodComposite _method;
|
||||
ArMessagePackRequestHandlerFunction _onRequest;
|
||||
size_t _contentLength;
|
||||
#if ARDUINOJSON_VERSION_MAJOR == 6
|
||||
size_t maxJsonBufferSize;
|
||||
#endif
|
||||
size_t _maxContentLength;
|
||||
protected:
|
||||
String _uri;
|
||||
WebRequestMethodComposite _method;
|
||||
ArMessagePackRequestHandlerFunction _onRequest;
|
||||
size_t _contentLength;
|
||||
#if ARDUINOJSON_VERSION_MAJOR == 6
|
||||
size_t maxJsonBufferSize;
|
||||
#endif
|
||||
size_t _maxContentLength;
|
||||
|
||||
public:
|
||||
#if ARDUINOJSON_VERSION_MAJOR == 6
|
||||
AsyncCallbackMessagePackWebHandler(const String& uri, ArMessagePackRequestHandlerFunction onRequest = nullptr, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE);
|
||||
#else
|
||||
AsyncCallbackMessagePackWebHandler(const String& uri, ArMessagePackRequestHandlerFunction onRequest = nullptr);
|
||||
#endif
|
||||
public:
|
||||
#if ARDUINOJSON_VERSION_MAJOR == 6
|
||||
AsyncCallbackMessagePackWebHandler(
|
||||
const String &uri, ArMessagePackRequestHandlerFunction onRequest = nullptr, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE
|
||||
);
|
||||
#else
|
||||
AsyncCallbackMessagePackWebHandler(const String &uri, ArMessagePackRequestHandlerFunction onRequest = nullptr);
|
||||
#endif
|
||||
|
||||
void setMethod(WebRequestMethodComposite method) { _method = method; }
|
||||
void setMaxContentLength(int maxContentLength) { _maxContentLength = maxContentLength; }
|
||||
void onRequest(ArMessagePackRequestHandlerFunction fn) { _onRequest = fn; }
|
||||
void setMethod(WebRequestMethodComposite method) {
|
||||
_method = method;
|
||||
}
|
||||
void setMaxContentLength(int maxContentLength) {
|
||||
_maxContentLength = maxContentLength;
|
||||
}
|
||||
void onRequest(ArMessagePackRequestHandlerFunction fn) {
|
||||
_onRequest = fn;
|
||||
}
|
||||
|
||||
bool canHandle(AsyncWebServerRequest* request) const override final;
|
||||
void handleRequest(AsyncWebServerRequest* request) override final;
|
||||
void handleUpload(__unused AsyncWebServerRequest* request, __unused const String& filename, __unused size_t index, __unused uint8_t* data, __unused size_t len, __unused bool final) override final {}
|
||||
void handleBody(AsyncWebServerRequest* request, uint8_t* data, size_t len, size_t index, size_t total) override final;
|
||||
bool isRequestHandlerTrivial() const override final { return !_onRequest; }
|
||||
bool canHandle(AsyncWebServerRequest *request) const override final;
|
||||
void handleRequest(AsyncWebServerRequest *request) override final;
|
||||
void handleUpload(
|
||||
__unused AsyncWebServerRequest *request, __unused const String &filename, __unused size_t index, __unused uint8_t *data, __unused size_t len,
|
||||
__unused bool final
|
||||
) override final {}
|
||||
void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override final;
|
||||
bool isRequestHandlerTrivial() const override final {
|
||||
return !_onRequest;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // ASYNC_MSG_PACK_SUPPORT == 1
|
||||
#endif // ASYNC_MSG_PACK_SUPPORT == 1
|
||||
|
|
|
@ -1,22 +1,32 @@
|
|||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
|
||||
|
||||
#include "ESPAsyncWebServer.h"
|
||||
|
||||
AsyncWebHeader::AsyncWebHeader(const String& data) {
|
||||
if (!data)
|
||||
AsyncWebHeader::AsyncWebHeader(const String &data) {
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
int index = data.indexOf(':');
|
||||
if (index < 0)
|
||||
if (index < 0) {
|
||||
return;
|
||||
}
|
||||
_name = data.substring(0, index);
|
||||
_value = data.substring(index + 2);
|
||||
}
|
||||
|
||||
String AsyncWebHeader::toString() const {
|
||||
String str;
|
||||
str.reserve(_name.length() + _value.length() + 2);
|
||||
str.concat(_name);
|
||||
str.concat((char)0x3a);
|
||||
str.concat((char)0x20);
|
||||
str.concat(_value);
|
||||
str.concat(asyncsrv::T_rn);
|
||||
if (str.reserve(_name.length() + _value.length() + 2)) {
|
||||
str.concat(_name);
|
||||
str.concat((char)0x3a);
|
||||
str.concat((char)0x20);
|
||||
str.concat(_value);
|
||||
str.concat(asyncsrv::T_rn);
|
||||
} else {
|
||||
#ifdef ESP32
|
||||
log_e("Failed to allocate");
|
||||
#endif
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
|
|
@ -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
|
File diff suppressed because it is too large
Load diff
|
@ -1,43 +1,26 @@
|
|||
/*
|
||||
Asynchronous WebServer library for Espressif MCUs
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
|
||||
|
||||
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
|
||||
This file is part of the esp8266 core for Arduino environment.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
#ifndef ASYNCWEBSOCKET_H_
|
||||
#define ASYNCWEBSOCKET_H_
|
||||
|
||||
#include <Arduino.h>
|
||||
#ifdef ESP32
|
||||
#include "../../ESP32Async-AsyncTCP/src/AsyncTCP.h"
|
||||
#include <mutex>
|
||||
#ifndef WS_MAX_QUEUED_MESSAGES
|
||||
#define WS_MAX_QUEUED_MESSAGES 32
|
||||
#endif
|
||||
#include "../../mathieucarbou-AsyncTCPSock/src/AsyncTCP.h"
|
||||
#include <mutex>
|
||||
#ifndef WS_MAX_QUEUED_MESSAGES
|
||||
#define WS_MAX_QUEUED_MESSAGES 32
|
||||
#endif
|
||||
#elif defined(ESP8266)
|
||||
#include <ESPAsyncTCP.h>
|
||||
#ifndef WS_MAX_QUEUED_MESSAGES
|
||||
#define WS_MAX_QUEUED_MESSAGES 8
|
||||
#endif
|
||||
#elif defined(TARGET_RP2040)
|
||||
#include <AsyncTCP_RP2040W.h>
|
||||
#ifndef WS_MAX_QUEUED_MESSAGES
|
||||
#define WS_MAX_QUEUED_MESSAGES 32
|
||||
#endif
|
||||
#include <ESPAsyncTCP.h>
|
||||
#ifndef WS_MAX_QUEUED_MESSAGES
|
||||
#define WS_MAX_QUEUED_MESSAGES 8
|
||||
#endif
|
||||
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
|
||||
#include <RPAsyncTCP.h>
|
||||
#ifndef WS_MAX_QUEUED_MESSAGES
|
||||
#define WS_MAX_QUEUED_MESSAGES 32
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include "ESPAsyncWebServer.h"
|
||||
|
@ -45,18 +28,18 @@
|
|||
#include <memory>
|
||||
|
||||
#ifdef ESP8266
|
||||
#include <Hash.h>
|
||||
#ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library
|
||||
#include <../src/Hash.h>
|
||||
#endif
|
||||
#include <Hash.h>
|
||||
#ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library
|
||||
#include <../src/Hash.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef DEFAULT_MAX_WS_CLIENTS
|
||||
#ifdef ESP32
|
||||
#define DEFAULT_MAX_WS_CLIENTS 8
|
||||
#else
|
||||
#define DEFAULT_MAX_WS_CLIENTS 4
|
||||
#endif
|
||||
#ifdef ESP32
|
||||
#define DEFAULT_MAX_WS_CLIENTS 8
|
||||
#else
|
||||
#define DEFAULT_MAX_WS_CLIENTS 4
|
||||
#endif
|
||||
#endif
|
||||
|
||||
using AsyncWebSocketSharedBuffer = std::shared_ptr<std::vector<uint8_t>>;
|
||||
|
@ -67,311 +50,367 @@ class AsyncWebSocketClient;
|
|||
class AsyncWebSocketControl;
|
||||
|
||||
typedef struct {
|
||||
/** Message type as defined by enum AwsFrameType.
|
||||
/** Message type as defined by enum AwsFrameType.
|
||||
* Note: Applications will only see WS_TEXT and WS_BINARY.
|
||||
* All other types are handled by the library. */
|
||||
uint8_t message_opcode;
|
||||
/** Frame number of a fragmented message. */
|
||||
uint32_t num;
|
||||
/** Is this the last frame in a fragmented message ?*/
|
||||
uint8_t final;
|
||||
/** Is this frame masked? */
|
||||
uint8_t masked;
|
||||
/** Message type as defined by enum AwsFrameType.
|
||||
uint8_t message_opcode;
|
||||
/** Frame number of a fragmented message. */
|
||||
uint32_t num;
|
||||
/** Is this the last frame in a fragmented message ?*/
|
||||
uint8_t final;
|
||||
/** Is this frame masked? */
|
||||
uint8_t masked;
|
||||
/** Message type as defined by enum AwsFrameType.
|
||||
* This value is the same as message_opcode for non-fragmented
|
||||
* messages, but may also be WS_CONTINUATION in a fragmented message. */
|
||||
uint8_t opcode;
|
||||
/** Length of the current frame.
|
||||
uint8_t opcode;
|
||||
/** Length of the current frame.
|
||||
* This equals the total length of the message if num == 0 && final == true */
|
||||
uint64_t len;
|
||||
/** Mask key */
|
||||
uint8_t mask[4];
|
||||
/** Offset of the data inside the current frame. */
|
||||
uint64_t index;
|
||||
uint64_t len;
|
||||
/** Mask key */
|
||||
uint8_t mask[4];
|
||||
/** Offset of the data inside the current frame. */
|
||||
uint64_t index;
|
||||
} AwsFrameInfo;
|
||||
|
||||
typedef enum { WS_DISCONNECTED,
|
||||
WS_CONNECTED,
|
||||
WS_DISCONNECTING } AwsClientStatus;
|
||||
typedef enum { WS_CONTINUATION,
|
||||
WS_TEXT,
|
||||
WS_BINARY,
|
||||
WS_DISCONNECT = 0x08,
|
||||
WS_PING,
|
||||
WS_PONG } AwsFrameType;
|
||||
typedef enum { WS_MSG_SENDING,
|
||||
WS_MSG_SENT,
|
||||
WS_MSG_ERROR } AwsMessageStatus;
|
||||
typedef enum { WS_EVT_CONNECT,
|
||||
WS_EVT_DISCONNECT,
|
||||
WS_EVT_PING,
|
||||
WS_EVT_PONG,
|
||||
WS_EVT_ERROR,
|
||||
WS_EVT_DATA } AwsEventType;
|
||||
typedef enum {
|
||||
WS_DISCONNECTED,
|
||||
WS_CONNECTED,
|
||||
WS_DISCONNECTING
|
||||
} AwsClientStatus;
|
||||
typedef enum {
|
||||
WS_CONTINUATION,
|
||||
WS_TEXT,
|
||||
WS_BINARY,
|
||||
WS_DISCONNECT = 0x08,
|
||||
WS_PING,
|
||||
WS_PONG
|
||||
} AwsFrameType;
|
||||
typedef enum {
|
||||
WS_MSG_SENDING,
|
||||
WS_MSG_SENT,
|
||||
WS_MSG_ERROR
|
||||
} AwsMessageStatus;
|
||||
typedef enum {
|
||||
WS_EVT_CONNECT,
|
||||
WS_EVT_DISCONNECT,
|
||||
WS_EVT_PING,
|
||||
WS_EVT_PONG,
|
||||
WS_EVT_ERROR,
|
||||
WS_EVT_DATA
|
||||
} AwsEventType;
|
||||
|
||||
class AsyncWebSocketMessageBuffer {
|
||||
friend AsyncWebSocket;
|
||||
friend AsyncWebSocketClient;
|
||||
friend AsyncWebSocket;
|
||||
friend AsyncWebSocketClient;
|
||||
|
||||
private:
|
||||
AsyncWebSocketSharedBuffer _buffer;
|
||||
private:
|
||||
AsyncWebSocketSharedBuffer _buffer;
|
||||
|
||||
public:
|
||||
AsyncWebSocketMessageBuffer() {}
|
||||
explicit AsyncWebSocketMessageBuffer(size_t size);
|
||||
AsyncWebSocketMessageBuffer(const uint8_t* data, size_t size);
|
||||
//~AsyncWebSocketMessageBuffer();
|
||||
bool reserve(size_t size);
|
||||
uint8_t* get() { return _buffer->data(); }
|
||||
size_t length() const { return _buffer->size(); }
|
||||
public:
|
||||
AsyncWebSocketMessageBuffer() {}
|
||||
explicit AsyncWebSocketMessageBuffer(size_t size);
|
||||
AsyncWebSocketMessageBuffer(const uint8_t *data, size_t size);
|
||||
//~AsyncWebSocketMessageBuffer();
|
||||
bool reserve(size_t size);
|
||||
uint8_t *get() {
|
||||
return _buffer->data();
|
||||
}
|
||||
size_t length() const {
|
||||
return _buffer->size();
|
||||
}
|
||||
};
|
||||
|
||||
class AsyncWebSocketMessage {
|
||||
private:
|
||||
AsyncWebSocketSharedBuffer _WSbuffer;
|
||||
uint8_t _opcode{WS_TEXT};
|
||||
bool _mask{false};
|
||||
AwsMessageStatus _status{WS_MSG_ERROR};
|
||||
size_t _sent{};
|
||||
size_t _ack{};
|
||||
size_t _acked{};
|
||||
private:
|
||||
AsyncWebSocketSharedBuffer _WSbuffer;
|
||||
uint8_t _opcode{WS_TEXT};
|
||||
bool _mask{false};
|
||||
AwsMessageStatus _status{WS_MSG_ERROR};
|
||||
size_t _sent{};
|
||||
size_t _ack{};
|
||||
size_t _acked{};
|
||||
|
||||
public:
|
||||
AsyncWebSocketMessage(AsyncWebSocketSharedBuffer buffer, uint8_t opcode = WS_TEXT, bool mask = false);
|
||||
public:
|
||||
AsyncWebSocketMessage(AsyncWebSocketSharedBuffer buffer, uint8_t opcode = WS_TEXT, bool mask = false);
|
||||
|
||||
bool finished() const { return _status != WS_MSG_SENDING; }
|
||||
bool betweenFrames() const { return _acked == _ack; }
|
||||
bool finished() const {
|
||||
return _status != WS_MSG_SENDING;
|
||||
}
|
||||
bool betweenFrames() const {
|
||||
return _acked == _ack;
|
||||
}
|
||||
|
||||
void ack(size_t len, uint32_t time);
|
||||
size_t send(AsyncClient* client);
|
||||
void ack(size_t len, uint32_t time);
|
||||
size_t send(AsyncClient *client);
|
||||
};
|
||||
|
||||
class AsyncWebSocketClient {
|
||||
private:
|
||||
AsyncClient* _client;
|
||||
AsyncWebSocket* _server;
|
||||
uint32_t _clientId;
|
||||
AwsClientStatus _status;
|
||||
private:
|
||||
AsyncClient *_client;
|
||||
AsyncWebSocket *_server;
|
||||
uint32_t _clientId;
|
||||
AwsClientStatus _status;
|
||||
#ifdef ESP32
|
||||
mutable std::mutex _lock;
|
||||
mutable std::mutex _lock;
|
||||
#endif
|
||||
std::deque<AsyncWebSocketControl> _controlQueue;
|
||||
std::deque<AsyncWebSocketMessage> _messageQueue;
|
||||
bool closeWhenFull = true;
|
||||
std::deque<AsyncWebSocketControl> _controlQueue;
|
||||
std::deque<AsyncWebSocketMessage> _messageQueue;
|
||||
bool closeWhenFull = true;
|
||||
|
||||
uint8_t _pstate;
|
||||
AwsFrameInfo _pinfo;
|
||||
uint8_t _pstate;
|
||||
AwsFrameInfo _pinfo;
|
||||
|
||||
uint32_t _lastMessageTime;
|
||||
uint32_t _keepAlivePeriod;
|
||||
uint32_t _lastMessageTime;
|
||||
uint32_t _keepAlivePeriod;
|
||||
|
||||
bool _queueControl(uint8_t opcode, const uint8_t* data = NULL, size_t len = 0, bool mask = false);
|
||||
bool _queueMessage(AsyncWebSocketSharedBuffer buffer, uint8_t opcode = WS_TEXT, bool mask = false);
|
||||
void _runQueue();
|
||||
void _clearQueue();
|
||||
bool _queueControl(uint8_t opcode, const uint8_t *data = NULL, size_t len = 0, bool mask = false);
|
||||
bool _queueMessage(AsyncWebSocketSharedBuffer buffer, uint8_t opcode = WS_TEXT, bool mask = false);
|
||||
void _runQueue();
|
||||
void _clearQueue();
|
||||
|
||||
public:
|
||||
void* _tempObject;
|
||||
public:
|
||||
void *_tempObject;
|
||||
|
||||
AsyncWebSocketClient(AsyncWebServerRequest* request, AsyncWebSocket* server);
|
||||
~AsyncWebSocketClient();
|
||||
AsyncWebSocketClient(AsyncWebServerRequest *request, AsyncWebSocket *server);
|
||||
~AsyncWebSocketClient();
|
||||
|
||||
// client id increments for the given server
|
||||
uint32_t id() const { return _clientId; }
|
||||
AwsClientStatus status() const { return _status; }
|
||||
AsyncClient* client() { return _client; }
|
||||
const AsyncClient* client() const { return _client; }
|
||||
AsyncWebSocket* server() { return _server; }
|
||||
const AsyncWebSocket* server() const { return _server; }
|
||||
AwsFrameInfo const& pinfo() const { return _pinfo; }
|
||||
// client id increments for the given server
|
||||
uint32_t id() const {
|
||||
return _clientId;
|
||||
}
|
||||
AwsClientStatus status() const {
|
||||
return _status;
|
||||
}
|
||||
AsyncClient *client() {
|
||||
return _client;
|
||||
}
|
||||
const AsyncClient *client() const {
|
||||
return _client;
|
||||
}
|
||||
AsyncWebSocket *server() {
|
||||
return _server;
|
||||
}
|
||||
const AsyncWebSocket *server() const {
|
||||
return _server;
|
||||
}
|
||||
AwsFrameInfo const &pinfo() const {
|
||||
return _pinfo;
|
||||
}
|
||||
|
||||
// - If "true" (default), the connection will be closed if the message queue is full.
|
||||
// This is the default behavior in yubox-node-org, which is not silently discarding messages but instead closes the connection.
|
||||
// The big issue with this behavior is that is can cause the UI to automatically re-create a new WS connection, which can be filled again,
|
||||
// and so on, causing a resource exhaustion.
|
||||
//
|
||||
// - If "false", the incoming message will be discarded if the queue is full.
|
||||
// This is the default behavior in the original ESPAsyncWebServer library from me-no-dev.
|
||||
// This behavior allows the best performance at the expense of unreliable message delivery in case the queue is full (some messages may be lost).
|
||||
//
|
||||
// - In any case, when the queue is full, a message is logged.
|
||||
// - IT is recommended to use the methods queueIsFull(), availableForWriteAll(), availableForWrite(clientId) to check if the queue is full before sending a message.
|
||||
//
|
||||
// Usage:
|
||||
// - can be set in the onEvent listener when connecting (event type is: WS_EVT_CONNECT)
|
||||
//
|
||||
// Use cases:,
|
||||
// - if using websocket to send logging messages, maybe some loss is acceptable.
|
||||
// - But if using websocket to send UI update messages, maybe the connection should be closed and the UI redrawn.
|
||||
void setCloseClientOnQueueFull(bool close) { closeWhenFull = close; }
|
||||
bool willCloseClientOnQueueFull() const { return closeWhenFull; }
|
||||
// - If "true" (default), the connection will be closed if the message queue is full.
|
||||
// This is the default behavior in yubox-node-org, which is not silently discarding messages but instead closes the connection.
|
||||
// The big issue with this behavior is that is can cause the UI to automatically re-create a new WS connection, which can be filled again,
|
||||
// and so on, causing a resource exhaustion.
|
||||
//
|
||||
// - If "false", the incoming message will be discarded if the queue is full.
|
||||
// This is the default behavior in the original ESPAsyncWebServer library from me-no-dev.
|
||||
// This behavior allows the best performance at the expense of unreliable message delivery in case the queue is full (some messages may be lost).
|
||||
//
|
||||
// - In any case, when the queue is full, a message is logged.
|
||||
// - IT is recommended to use the methods queueIsFull(), availableForWriteAll(), availableForWrite(clientId) to check if the queue is full before sending a message.
|
||||
//
|
||||
// Usage:
|
||||
// - can be set in the onEvent listener when connecting (event type is: WS_EVT_CONNECT)
|
||||
//
|
||||
// Use cases:,
|
||||
// - if using websocket to send logging messages, maybe some loss is acceptable.
|
||||
// - But if using websocket to send UI update messages, maybe the connection should be closed and the UI redrawn.
|
||||
void setCloseClientOnQueueFull(bool close) {
|
||||
closeWhenFull = close;
|
||||
}
|
||||
bool willCloseClientOnQueueFull() const {
|
||||
return closeWhenFull;
|
||||
}
|
||||
|
||||
IPAddress remoteIP() const;
|
||||
uint16_t remotePort() const;
|
||||
IPAddress remoteIP() const;
|
||||
uint16_t remotePort() const;
|
||||
|
||||
bool shouldBeDeleted() const { return !_client; }
|
||||
bool shouldBeDeleted() const {
|
||||
return !_client;
|
||||
}
|
||||
|
||||
// control frames
|
||||
void close(uint16_t code = 0, const char* message = NULL);
|
||||
bool ping(const uint8_t* data = NULL, size_t len = 0);
|
||||
// control frames
|
||||
void close(uint16_t code = 0, const char *message = NULL);
|
||||
bool ping(const uint8_t *data = NULL, size_t len = 0);
|
||||
|
||||
// set auto-ping period in seconds. disabled if zero (default)
|
||||
void keepAlivePeriod(uint16_t seconds) {
|
||||
_keepAlivePeriod = seconds * 1000;
|
||||
}
|
||||
uint16_t keepAlivePeriod() {
|
||||
return (uint16_t)(_keepAlivePeriod / 1000);
|
||||
}
|
||||
// set auto-ping period in seconds. disabled if zero (default)
|
||||
void keepAlivePeriod(uint16_t seconds) {
|
||||
_keepAlivePeriod = seconds * 1000;
|
||||
}
|
||||
uint16_t keepAlivePeriod() {
|
||||
return (uint16_t)(_keepAlivePeriod / 1000);
|
||||
}
|
||||
|
||||
// data packets
|
||||
void message(AsyncWebSocketSharedBuffer buffer, uint8_t opcode = WS_TEXT, bool mask = false) { _queueMessage(buffer, opcode, mask); }
|
||||
bool queueIsFull() const;
|
||||
size_t queueLen() const;
|
||||
// data packets
|
||||
void message(AsyncWebSocketSharedBuffer buffer, uint8_t opcode = WS_TEXT, bool mask = false) {
|
||||
_queueMessage(buffer, opcode, mask);
|
||||
}
|
||||
bool queueIsFull() const;
|
||||
size_t queueLen() const;
|
||||
|
||||
size_t printf(const char* format, ...) __attribute__((format(printf, 2, 3)));
|
||||
size_t printf(const char *format, ...) __attribute__((format(printf, 2, 3)));
|
||||
|
||||
bool text(AsyncWebSocketSharedBuffer buffer);
|
||||
bool text(const uint8_t* message, size_t len);
|
||||
bool text(const char* message, size_t len);
|
||||
bool text(const char* message);
|
||||
bool text(const String& message);
|
||||
bool text(AsyncWebSocketMessageBuffer* buffer);
|
||||
bool text(AsyncWebSocketSharedBuffer buffer);
|
||||
bool text(const uint8_t *message, size_t len);
|
||||
bool text(const char *message, size_t len);
|
||||
bool text(const char *message);
|
||||
bool text(const String &message);
|
||||
bool text(AsyncWebSocketMessageBuffer *buffer);
|
||||
|
||||
bool binary(AsyncWebSocketSharedBuffer buffer);
|
||||
bool binary(const uint8_t* message, size_t len);
|
||||
bool binary(const char* message, size_t len);
|
||||
bool binary(const char* message);
|
||||
bool binary(const String& message);
|
||||
bool binary(AsyncWebSocketMessageBuffer* buffer);
|
||||
bool binary(AsyncWebSocketSharedBuffer buffer);
|
||||
bool binary(const uint8_t *message, size_t len);
|
||||
bool binary(const char *message, size_t len);
|
||||
bool binary(const char *message);
|
||||
bool binary(const String &message);
|
||||
bool binary(AsyncWebSocketMessageBuffer *buffer);
|
||||
|
||||
bool canSend() const;
|
||||
bool canSend() const;
|
||||
|
||||
// system callbacks (do not call)
|
||||
void _onAck(size_t len, uint32_t time);
|
||||
void _onError(int8_t);
|
||||
void _onPoll();
|
||||
void _onTimeout(uint32_t time);
|
||||
void _onDisconnect();
|
||||
void _onData(void* pbuf, size_t plen);
|
||||
// system callbacks (do not call)
|
||||
void _onAck(size_t len, uint32_t time);
|
||||
void _onError(int8_t);
|
||||
void _onPoll();
|
||||
void _onTimeout(uint32_t time);
|
||||
void _onDisconnect();
|
||||
void _onData(void *pbuf, size_t plen);
|
||||
|
||||
#ifdef ESP8266
|
||||
size_t printf_P(PGM_P formatP, ...) __attribute__((format(printf, 2, 3)));
|
||||
bool text(const __FlashStringHelper* message);
|
||||
bool binary(const __FlashStringHelper* message, size_t len);
|
||||
size_t printf_P(PGM_P formatP, ...) __attribute__((format(printf, 2, 3)));
|
||||
bool text(const __FlashStringHelper *message);
|
||||
bool binary(const __FlashStringHelper *message, size_t len);
|
||||
#endif
|
||||
};
|
||||
|
||||
using AwsHandshakeHandler = std::function<bool(AsyncWebServerRequest* request)>;
|
||||
using AwsEventHandler = std::function<void(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len)>;
|
||||
using AwsHandshakeHandler = std::function<bool(AsyncWebServerRequest *request)>;
|
||||
using AwsEventHandler = std::function<void(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len)>;
|
||||
|
||||
// WebServer Handler implementation that plays the role of a socket server
|
||||
class AsyncWebSocket : public AsyncWebHandler {
|
||||
private:
|
||||
String _url;
|
||||
std::list<AsyncWebSocketClient> _clients;
|
||||
uint32_t _cNextId;
|
||||
AwsEventHandler _eventHandler{nullptr};
|
||||
AwsHandshakeHandler _handshakeHandler;
|
||||
bool _enabled;
|
||||
private:
|
||||
String _url;
|
||||
std::list<AsyncWebSocketClient> _clients;
|
||||
uint32_t _cNextId;
|
||||
AwsEventHandler _eventHandler{nullptr};
|
||||
AwsHandshakeHandler _handshakeHandler;
|
||||
bool _enabled;
|
||||
#ifdef ESP32
|
||||
mutable std::mutex _lock;
|
||||
mutable std::mutex _lock;
|
||||
#endif
|
||||
|
||||
public:
|
||||
typedef enum {
|
||||
DISCARDED = 0,
|
||||
ENQUEUED = 1,
|
||||
PARTIALLY_ENQUEUED = 2,
|
||||
} SendStatus;
|
||||
public:
|
||||
typedef enum {
|
||||
DISCARDED = 0,
|
||||
ENQUEUED = 1,
|
||||
PARTIALLY_ENQUEUED = 2,
|
||||
} SendStatus;
|
||||
|
||||
explicit AsyncWebSocket(const char* url) : _url(url), _cNextId(1), _enabled(true) {}
|
||||
AsyncWebSocket(const String& url) : _url(url), _cNextId(1), _enabled(true) {}
|
||||
~AsyncWebSocket() {};
|
||||
const char* url() const { return _url.c_str(); }
|
||||
void enable(bool e) { _enabled = e; }
|
||||
bool enabled() const { return _enabled; }
|
||||
bool availableForWriteAll();
|
||||
bool availableForWrite(uint32_t id);
|
||||
explicit AsyncWebSocket(const char *url) : _url(url), _cNextId(1), _enabled(true) {}
|
||||
AsyncWebSocket(const String &url) : _url(url), _cNextId(1), _enabled(true) {}
|
||||
~AsyncWebSocket(){};
|
||||
const char *url() const {
|
||||
return _url.c_str();
|
||||
}
|
||||
void enable(bool e) {
|
||||
_enabled = e;
|
||||
}
|
||||
bool enabled() const {
|
||||
return _enabled;
|
||||
}
|
||||
bool availableForWriteAll();
|
||||
bool availableForWrite(uint32_t id);
|
||||
|
||||
size_t count() const;
|
||||
AsyncWebSocketClient* client(uint32_t id);
|
||||
bool hasClient(uint32_t id) { return client(id) != nullptr; }
|
||||
size_t count() const;
|
||||
AsyncWebSocketClient *client(uint32_t id);
|
||||
bool hasClient(uint32_t id) {
|
||||
return client(id) != nullptr;
|
||||
}
|
||||
|
||||
void close(uint32_t id, uint16_t code = 0, const char* message = NULL);
|
||||
void closeAll(uint16_t code = 0, const char* message = NULL);
|
||||
void cleanupClients(uint16_t maxClients = DEFAULT_MAX_WS_CLIENTS);
|
||||
void close(uint32_t id, uint16_t code = 0, const char *message = NULL);
|
||||
void closeAll(uint16_t code = 0, const char *message = NULL);
|
||||
void cleanupClients(uint16_t maxClients = DEFAULT_MAX_WS_CLIENTS);
|
||||
|
||||
bool ping(uint32_t id, const uint8_t* data = NULL, size_t len = 0);
|
||||
SendStatus pingAll(const uint8_t* data = NULL, size_t len = 0); // done
|
||||
bool ping(uint32_t id, const uint8_t *data = NULL, size_t len = 0);
|
||||
SendStatus pingAll(const uint8_t *data = NULL, size_t len = 0); // done
|
||||
|
||||
bool text(uint32_t id, const uint8_t* message, size_t len);
|
||||
bool text(uint32_t id, const char* message, size_t len);
|
||||
bool text(uint32_t id, const char* message);
|
||||
bool text(uint32_t id, const String& message);
|
||||
bool text(uint32_t id, AsyncWebSocketMessageBuffer* buffer);
|
||||
bool text(uint32_t id, AsyncWebSocketSharedBuffer buffer);
|
||||
bool text(uint32_t id, const uint8_t *message, size_t len);
|
||||
bool text(uint32_t id, const char *message, size_t len);
|
||||
bool text(uint32_t id, const char *message);
|
||||
bool text(uint32_t id, const String &message);
|
||||
bool text(uint32_t id, AsyncWebSocketMessageBuffer *buffer);
|
||||
bool text(uint32_t id, AsyncWebSocketSharedBuffer buffer);
|
||||
|
||||
SendStatus textAll(const uint8_t* message, size_t len);
|
||||
SendStatus textAll(const char* message, size_t len);
|
||||
SendStatus textAll(const char* message);
|
||||
SendStatus textAll(const String& message);
|
||||
SendStatus textAll(AsyncWebSocketMessageBuffer* buffer);
|
||||
SendStatus textAll(AsyncWebSocketSharedBuffer buffer);
|
||||
SendStatus textAll(const uint8_t *message, size_t len);
|
||||
SendStatus textAll(const char *message, size_t len);
|
||||
SendStatus textAll(const char *message);
|
||||
SendStatus textAll(const String &message);
|
||||
SendStatus textAll(AsyncWebSocketMessageBuffer *buffer);
|
||||
SendStatus textAll(AsyncWebSocketSharedBuffer buffer);
|
||||
|
||||
bool binary(uint32_t id, const uint8_t* message, size_t len);
|
||||
bool binary(uint32_t id, const char* message, size_t len);
|
||||
bool binary(uint32_t id, const char* message);
|
||||
bool binary(uint32_t id, const String& message);
|
||||
bool binary(uint32_t id, AsyncWebSocketMessageBuffer* buffer);
|
||||
bool binary(uint32_t id, AsyncWebSocketSharedBuffer buffer);
|
||||
bool binary(uint32_t id, const uint8_t *message, size_t len);
|
||||
bool binary(uint32_t id, const char *message, size_t len);
|
||||
bool binary(uint32_t id, const char *message);
|
||||
bool binary(uint32_t id, const String &message);
|
||||
bool binary(uint32_t id, AsyncWebSocketMessageBuffer *buffer);
|
||||
bool binary(uint32_t id, AsyncWebSocketSharedBuffer buffer);
|
||||
|
||||
SendStatus binaryAll(const uint8_t* message, size_t len);
|
||||
SendStatus binaryAll(const char* message, size_t len);
|
||||
SendStatus binaryAll(const char* message);
|
||||
SendStatus binaryAll(const String& message);
|
||||
SendStatus binaryAll(AsyncWebSocketMessageBuffer* buffer);
|
||||
SendStatus binaryAll(AsyncWebSocketSharedBuffer buffer);
|
||||
SendStatus binaryAll(const uint8_t *message, size_t len);
|
||||
SendStatus binaryAll(const char *message, size_t len);
|
||||
SendStatus binaryAll(const char *message);
|
||||
SendStatus binaryAll(const String &message);
|
||||
SendStatus binaryAll(AsyncWebSocketMessageBuffer *buffer);
|
||||
SendStatus binaryAll(AsyncWebSocketSharedBuffer buffer);
|
||||
|
||||
size_t printf(uint32_t id, const char* format, ...) __attribute__((format(printf, 3, 4)));
|
||||
size_t printfAll(const char* format, ...) __attribute__((format(printf, 2, 3)));
|
||||
size_t printf(uint32_t id, const char *format, ...) __attribute__((format(printf, 3, 4)));
|
||||
size_t printfAll(const char *format, ...) __attribute__((format(printf, 2, 3)));
|
||||
|
||||
#ifdef ESP8266
|
||||
bool text(uint32_t id, const __FlashStringHelper* message);
|
||||
SendStatus textAll(const __FlashStringHelper* message);
|
||||
bool binary(uint32_t id, const __FlashStringHelper* message, size_t len);
|
||||
SendStatus binaryAll(const __FlashStringHelper* message, size_t len);
|
||||
size_t printf_P(uint32_t id, PGM_P formatP, ...) __attribute__((format(printf, 3, 4)));
|
||||
size_t printfAll_P(PGM_P formatP, ...) __attribute__((format(printf, 2, 3)));
|
||||
bool text(uint32_t id, const __FlashStringHelper *message);
|
||||
SendStatus textAll(const __FlashStringHelper *message);
|
||||
bool binary(uint32_t id, const __FlashStringHelper *message, size_t len);
|
||||
SendStatus binaryAll(const __FlashStringHelper *message, size_t len);
|
||||
size_t printf_P(uint32_t id, PGM_P formatP, ...) __attribute__((format(printf, 3, 4)));
|
||||
size_t printfAll_P(PGM_P formatP, ...) __attribute__((format(printf, 2, 3)));
|
||||
#endif
|
||||
|
||||
void onEvent(AwsEventHandler handler) { _eventHandler = handler; }
|
||||
void handleHandshake(AwsHandshakeHandler handler) { _handshakeHandler = handler; }
|
||||
void onEvent(AwsEventHandler handler) {
|
||||
_eventHandler = handler;
|
||||
}
|
||||
void handleHandshake(AwsHandshakeHandler handler) {
|
||||
_handshakeHandler = handler;
|
||||
}
|
||||
|
||||
// system callbacks (do not call)
|
||||
uint32_t _getNextId() { return _cNextId++; }
|
||||
AsyncWebSocketClient* _newClient(AsyncWebServerRequest* request);
|
||||
void _handleEvent(AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len);
|
||||
bool canHandle(AsyncWebServerRequest* request) const override final;
|
||||
void handleRequest(AsyncWebServerRequest* request) override final;
|
||||
// system callbacks (do not call)
|
||||
uint32_t _getNextId() {
|
||||
return _cNextId++;
|
||||
}
|
||||
AsyncWebSocketClient *_newClient(AsyncWebServerRequest *request);
|
||||
void _handleEvent(AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len);
|
||||
bool canHandle(AsyncWebServerRequest *request) const override final;
|
||||
void handleRequest(AsyncWebServerRequest *request) override final;
|
||||
|
||||
// messagebuffer functions/objects.
|
||||
AsyncWebSocketMessageBuffer* makeBuffer(size_t size = 0);
|
||||
AsyncWebSocketMessageBuffer* makeBuffer(const uint8_t* data, size_t size);
|
||||
// messagebuffer functions/objects.
|
||||
AsyncWebSocketMessageBuffer *makeBuffer(size_t size = 0);
|
||||
AsyncWebSocketMessageBuffer *makeBuffer(const uint8_t *data, size_t size);
|
||||
|
||||
std::list<AsyncWebSocketClient>& getClients() { return _clients; }
|
||||
std::list<AsyncWebSocketClient> &getClients() {
|
||||
return _clients;
|
||||
}
|
||||
};
|
||||
|
||||
// WebServer response to authenticate the socket and detach the tcp client from the web server request
|
||||
class AsyncWebSocketResponse : public AsyncWebServerResponse {
|
||||
private:
|
||||
String _content;
|
||||
AsyncWebSocket* _server;
|
||||
private:
|
||||
String _content;
|
||||
AsyncWebSocket *_server;
|
||||
|
||||
public:
|
||||
AsyncWebSocketResponse(const String& key, AsyncWebSocket* server);
|
||||
void _respond(AsyncWebServerRequest* request);
|
||||
size_t _ack(AsyncWebServerRequest* request, size_t len, uint32_t time);
|
||||
bool _sourceValid() const { return true; }
|
||||
public:
|
||||
AsyncWebSocketResponse(const String &key, AsyncWebSocket *server);
|
||||
void _respond(AsyncWebServerRequest *request);
|
||||
size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time);
|
||||
bool _sourceValid() const {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* ASYNCWEBSOCKET_H_ */
|
||||
|
|
|
@ -281,4 +281,4 @@ void SHA1Builder::getBytes(uint8_t *output) {
|
|||
memcpy(output, hash, SHA1_HASH_SIZE);
|
||||
}
|
||||
|
||||
#endif // ESP_IDF_VERSION_MAJOR < 5
|
||||
#endif // ESP_IDF_VERSION_MAJOR < 5
|
||||
|
|
|
@ -24,21 +24,21 @@
|
|||
#define SHA1_HASH_SIZE 20
|
||||
|
||||
class SHA1Builder {
|
||||
private:
|
||||
uint32_t total[2]; /* number of bytes processed */
|
||||
uint32_t state[5]; /* intermediate digest state */
|
||||
unsigned char buffer[64]; /* data block being processed */
|
||||
uint8_t hash[SHA1_HASH_SIZE]; /* SHA-1 result */
|
||||
private:
|
||||
uint32_t total[2]; /* number of bytes processed */
|
||||
uint32_t state[5]; /* intermediate digest state */
|
||||
unsigned char buffer[64]; /* data block being processed */
|
||||
uint8_t hash[SHA1_HASH_SIZE]; /* SHA-1 result */
|
||||
|
||||
void process(const uint8_t* data);
|
||||
void process(const uint8_t *data);
|
||||
|
||||
public:
|
||||
void begin();
|
||||
void add(const uint8_t* data, size_t len);
|
||||
void calculate();
|
||||
void getBytes(uint8_t* output);
|
||||
public:
|
||||
void begin();
|
||||
void add(const uint8_t *data, size_t len);
|
||||
void calculate();
|
||||
void getBytes(uint8_t *output);
|
||||
};
|
||||
|
||||
#endif // SHA1Builder_h
|
||||
#endif // SHA1Builder_h
|
||||
|
||||
#endif // ESP_IDF_VERSION_MAJOR < 5
|
||||
#endif // ESP_IDF_VERSION_MAJOR < 5
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
|
||||
|
||||
#include "ChunkPrint.h"
|
||||
|
||||
ChunkPrint::ChunkPrint(uint8_t* destination, size_t from, size_t len)
|
||||
: _destination(destination), _to_skip(from), _to_write(len), _pos{0} {}
|
||||
ChunkPrint::ChunkPrint(uint8_t *destination, size_t from, size_t len) : _destination(destination), _to_skip(from), _to_write(len), _pos{0} {}
|
||||
|
||||
size_t ChunkPrint::write(uint8_t c) {
|
||||
if (_to_skip > 0) {
|
||||
|
@ -13,4 +15,4 @@ size_t ChunkPrint::write(uint8_t c) {
|
|||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +1,23 @@
|
|||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
|
||||
|
||||
#ifndef CHUNKPRINT_H
|
||||
#define CHUNKPRINT_H
|
||||
|
||||
#include <Print.h>
|
||||
|
||||
class ChunkPrint : public Print {
|
||||
private:
|
||||
uint8_t* _destination;
|
||||
size_t _to_skip;
|
||||
size_t _to_write;
|
||||
size_t _pos;
|
||||
private:
|
||||
uint8_t *_destination;
|
||||
size_t _to_skip;
|
||||
size_t _to_write;
|
||||
size_t _pos;
|
||||
|
||||
public:
|
||||
ChunkPrint(uint8_t* destination, size_t from, size_t len);
|
||||
size_t write(uint8_t c);
|
||||
size_t write(const uint8_t* buffer, size_t size) { return this->Print::write(buffer, size); }
|
||||
public:
|
||||
ChunkPrint(uint8_t *destination, size_t from, size_t len);
|
||||
size_t write(uint8_t c);
|
||||
size_t write(const uint8_t *buffer, size_t size) {
|
||||
return this->Print::write(buffer, size);
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,70 +1,85 @@
|
|||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
|
||||
|
||||
#include "WebAuthentication.h"
|
||||
#include "ESPAsyncWebServer.h"
|
||||
|
||||
AsyncMiddlewareChain::~AsyncMiddlewareChain() {
|
||||
for (AsyncMiddleware* m : _middlewares)
|
||||
if (m->_freeOnRemoval)
|
||||
for (AsyncMiddleware *m : _middlewares) {
|
||||
if (m->_freeOnRemoval) {
|
||||
delete m;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AsyncMiddlewareChain::addMiddleware(ArMiddlewareCallback fn) {
|
||||
AsyncMiddlewareFunction* m = new AsyncMiddlewareFunction(fn);
|
||||
AsyncMiddlewareFunction *m = new AsyncMiddlewareFunction(fn);
|
||||
m->_freeOnRemoval = true;
|
||||
_middlewares.emplace_back(m);
|
||||
}
|
||||
|
||||
void AsyncMiddlewareChain::addMiddleware(AsyncMiddleware* middleware) {
|
||||
if (middleware)
|
||||
void AsyncMiddlewareChain::addMiddleware(AsyncMiddleware *middleware) {
|
||||
if (middleware) {
|
||||
_middlewares.emplace_back(middleware);
|
||||
}
|
||||
}
|
||||
|
||||
void AsyncMiddlewareChain::addMiddlewares(std::vector<AsyncMiddleware*> middlewares) {
|
||||
for (AsyncMiddleware* m : middlewares)
|
||||
void AsyncMiddlewareChain::addMiddlewares(std::vector<AsyncMiddleware *> middlewares) {
|
||||
for (AsyncMiddleware *m : middlewares) {
|
||||
addMiddleware(m);
|
||||
}
|
||||
}
|
||||
|
||||
bool AsyncMiddlewareChain::removeMiddleware(AsyncMiddleware* middleware) {
|
||||
bool AsyncMiddlewareChain::removeMiddleware(AsyncMiddleware *middleware) {
|
||||
// remove all middlewares from _middlewares vector being equal to middleware, delete them having _freeOnRemoval flag to true and resize the vector.
|
||||
const size_t size = _middlewares.size();
|
||||
_middlewares.erase(std::remove_if(_middlewares.begin(), _middlewares.end(), [middleware](AsyncMiddleware* m) {
|
||||
if (m == middleware) {
|
||||
if (m->_freeOnRemoval)
|
||||
delete m;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}),
|
||||
_middlewares.end());
|
||||
_middlewares.erase(
|
||||
std::remove_if(
|
||||
_middlewares.begin(), _middlewares.end(),
|
||||
[middleware](AsyncMiddleware *m) {
|
||||
if (m == middleware) {
|
||||
if (m->_freeOnRemoval) {
|
||||
delete m;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
),
|
||||
_middlewares.end()
|
||||
);
|
||||
return size != _middlewares.size();
|
||||
}
|
||||
|
||||
void AsyncMiddlewareChain::_runChain(AsyncWebServerRequest* request, ArMiddlewareNext finalizer) {
|
||||
if (!_middlewares.size())
|
||||
void AsyncMiddlewareChain::_runChain(AsyncWebServerRequest *request, ArMiddlewareNext finalizer) {
|
||||
if (!_middlewares.size()) {
|
||||
return finalizer();
|
||||
}
|
||||
ArMiddlewareNext next;
|
||||
std::list<AsyncMiddleware*>::iterator it = _middlewares.begin();
|
||||
std::list<AsyncMiddleware *>::iterator it = _middlewares.begin();
|
||||
next = [this, &next, &it, request, finalizer]() {
|
||||
if (it == _middlewares.end())
|
||||
if (it == _middlewares.end()) {
|
||||
return finalizer();
|
||||
AsyncMiddleware* m = *it;
|
||||
}
|
||||
AsyncMiddleware *m = *it;
|
||||
it++;
|
||||
return m->run(request, next);
|
||||
};
|
||||
return next();
|
||||
}
|
||||
|
||||
void AsyncAuthenticationMiddleware::setUsername(const char* username) {
|
||||
void AsyncAuthenticationMiddleware::setUsername(const char *username) {
|
||||
_username = username;
|
||||
_hasCreds = _username.length() && _credentials.length();
|
||||
}
|
||||
|
||||
void AsyncAuthenticationMiddleware::setPassword(const char* password) {
|
||||
void AsyncAuthenticationMiddleware::setPassword(const char *password) {
|
||||
_credentials = password;
|
||||
_hash = false;
|
||||
_hasCreds = _username.length() && _credentials.length();
|
||||
}
|
||||
|
||||
void AsyncAuthenticationMiddleware::setPasswordHash(const char* hash) {
|
||||
void AsyncAuthenticationMiddleware::setPasswordHash(const char *hash) {
|
||||
_credentials = hash;
|
||||
_hash = _credentials.length();
|
||||
_hasCreds = _username.length() && _credentials.length();
|
||||
|
@ -72,71 +87,86 @@ void AsyncAuthenticationMiddleware::setPasswordHash(const char* hash) {
|
|||
|
||||
bool AsyncAuthenticationMiddleware::generateHash() {
|
||||
// ensure we have all the necessary data
|
||||
if (!_hasCreds)
|
||||
if (!_hasCreds) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// if we already have a hash, do nothing
|
||||
if (_hash)
|
||||
if (_hash) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (_authMethod) {
|
||||
case AsyncAuthType::AUTH_DIGEST:
|
||||
_credentials = generateDigestHash(_username.c_str(), _credentials.c_str(), _realm.c_str());
|
||||
_hash = true;
|
||||
return true;
|
||||
if (_credentials.length()) {
|
||||
_hash = true;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
case AsyncAuthType::AUTH_BASIC:
|
||||
_credentials = generateBasicHash(_username.c_str(), _credentials.c_str());
|
||||
_hash = true;
|
||||
return true;
|
||||
if (_credentials.length()) {
|
||||
_hash = true;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
default:
|
||||
return false;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool AsyncAuthenticationMiddleware::allowed(AsyncWebServerRequest* request) const {
|
||||
if (_authMethod == AsyncAuthType::AUTH_NONE)
|
||||
bool AsyncAuthenticationMiddleware::allowed(AsyncWebServerRequest *request) const {
|
||||
if (_authMethod == AsyncAuthType::AUTH_NONE) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_authMethod == AsyncAuthType::AUTH_DENIED)
|
||||
if (_authMethod == AsyncAuthType::AUTH_DENIED) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_hasCreds)
|
||||
if (!_hasCreds) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return request->authenticate(_username.c_str(), _credentials.c_str(), _realm.c_str(), _hash);
|
||||
}
|
||||
|
||||
void AsyncAuthenticationMiddleware::run(AsyncWebServerRequest* request, ArMiddlewareNext next) {
|
||||
void AsyncAuthenticationMiddleware::run(AsyncWebServerRequest *request, ArMiddlewareNext next) {
|
||||
return allowed(request) ? next() : request->requestAuthentication(_authMethod, _realm.c_str(), _authFailMsg.c_str());
|
||||
}
|
||||
|
||||
void AsyncHeaderFreeMiddleware::run(AsyncWebServerRequest* request, ArMiddlewareNext next) {
|
||||
std::vector<const char*> reqHeaders;
|
||||
request->getHeaderNames(reqHeaders);
|
||||
for (const char* h : reqHeaders) {
|
||||
void AsyncHeaderFreeMiddleware::run(AsyncWebServerRequest *request, ArMiddlewareNext next) {
|
||||
std::list<const char *> toRemove;
|
||||
for (auto &h : request->getHeaders()) {
|
||||
bool keep = false;
|
||||
for (const char* k : _toKeep) {
|
||||
if (strcasecmp(h, k) == 0) {
|
||||
for (const char *k : _toKeep) {
|
||||
if (strcasecmp(h.name().c_str(), k) == 0) {
|
||||
keep = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!keep) {
|
||||
request->removeHeader(h);
|
||||
toRemove.push_back(h.name().c_str());
|
||||
}
|
||||
}
|
||||
for (const char *h : toRemove) {
|
||||
request->removeHeader(h);
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
void AsyncHeaderFilterMiddleware::run(AsyncWebServerRequest* request, ArMiddlewareNext next) {
|
||||
for (auto it = _toRemove.begin(); it != _toRemove.end(); ++it)
|
||||
void AsyncHeaderFilterMiddleware::run(AsyncWebServerRequest *request, ArMiddlewareNext next) {
|
||||
for (auto it = _toRemove.begin(); it != _toRemove.end(); ++it) {
|
||||
request->removeHeader(*it);
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
void AsyncLoggingMiddleware::run(AsyncWebServerRequest* request, ArMiddlewareNext next) {
|
||||
void AsyncLoggingMiddleware::run(AsyncWebServerRequest *request, ArMiddlewareNext next) {
|
||||
if (!isEnabled()) {
|
||||
next();
|
||||
return;
|
||||
|
@ -152,7 +182,7 @@ void AsyncLoggingMiddleware::run(AsyncWebServerRequest* request, ArMiddlewareNex
|
|||
_out->print(request->url().c_str());
|
||||
_out->print(F(" HTTP/1."));
|
||||
_out->println(request->version());
|
||||
for (auto& h : request->getHeaders()) {
|
||||
for (auto &h : request->getHeaders()) {
|
||||
if (h.value().length()) {
|
||||
_out->print('>');
|
||||
_out->print(' ');
|
||||
|
@ -166,7 +196,7 @@ void AsyncLoggingMiddleware::run(AsyncWebServerRequest* request, ArMiddlewareNex
|
|||
uint32_t elapsed = millis();
|
||||
next();
|
||||
elapsed = millis() - elapsed;
|
||||
AsyncWebServerResponse* response = request->getResponse();
|
||||
AsyncWebServerResponse *response = request->getResponse();
|
||||
if (response) {
|
||||
_out->print(F("* Processed in "));
|
||||
_out->print(elapsed);
|
||||
|
@ -178,7 +208,7 @@ void AsyncLoggingMiddleware::run(AsyncWebServerRequest* request, ArMiddlewareNex
|
|||
_out->print(response->code());
|
||||
_out->print(' ');
|
||||
_out->println(AsyncWebServerResponse::responseCodeToString(response->code()));
|
||||
for (auto& h : response->getHeaders()) {
|
||||
for (auto &h : response->getHeaders()) {
|
||||
if (h.value().length()) {
|
||||
_out->print('<');
|
||||
_out->print(' ');
|
||||
|
@ -194,7 +224,7 @@ void AsyncLoggingMiddleware::run(AsyncWebServerRequest* request, ArMiddlewareNex
|
|||
}
|
||||
}
|
||||
|
||||
void AsyncCorsMiddleware::addCORSHeaders(AsyncWebServerResponse* response) {
|
||||
void AsyncCorsMiddleware::addCORSHeaders(AsyncWebServerResponse *response) {
|
||||
response->addHeader(asyncsrv::T_CORS_ACAO, _origin.c_str());
|
||||
response->addHeader(asyncsrv::T_CORS_ACAM, _methods.c_str());
|
||||
response->addHeader(asyncsrv::T_CORS_ACAH, _headers.c_str());
|
||||
|
@ -202,12 +232,12 @@ void AsyncCorsMiddleware::addCORSHeaders(AsyncWebServerResponse* response) {
|
|||
response->addHeader(asyncsrv::T_CORS_ACMA, String(_maxAge).c_str());
|
||||
}
|
||||
|
||||
void AsyncCorsMiddleware::run(AsyncWebServerRequest* request, ArMiddlewareNext next) {
|
||||
void AsyncCorsMiddleware::run(AsyncWebServerRequest *request, ArMiddlewareNext next) {
|
||||
// Origin header ? => CORS handling
|
||||
if (request->hasHeader(asyncsrv::T_CORS_O)) {
|
||||
// check if this is a preflight request => handle it and return
|
||||
if (request->method() == HTTP_OPTIONS) {
|
||||
AsyncWebServerResponse* response = request->beginResponse(200);
|
||||
AsyncWebServerResponse *response = request->beginResponse(200);
|
||||
addCORSHeaders(response);
|
||||
request->send(response);
|
||||
return;
|
||||
|
@ -215,7 +245,7 @@ void AsyncCorsMiddleware::run(AsyncWebServerRequest* request, ArMiddlewareNext n
|
|||
|
||||
// CORS request, no options => let the request pass and add CORS headers after
|
||||
next();
|
||||
AsyncWebServerResponse* response = request->getResponse();
|
||||
AsyncWebServerResponse *response = request->getResponse();
|
||||
if (response) {
|
||||
addCORSHeaders(response);
|
||||
}
|
||||
|
@ -226,11 +256,12 @@ void AsyncCorsMiddleware::run(AsyncWebServerRequest* request, ArMiddlewareNext n
|
|||
}
|
||||
}
|
||||
|
||||
bool AsyncRateLimitMiddleware::isRequestAllowed(uint32_t& retryAfterSeconds) {
|
||||
bool AsyncRateLimitMiddleware::isRequestAllowed(uint32_t &retryAfterSeconds) {
|
||||
uint32_t now = millis();
|
||||
|
||||
while (!_requestTimes.empty() && _requestTimes.front() <= now - _windowSizeMillis)
|
||||
while (!_requestTimes.empty() && _requestTimes.front() <= now - _windowSizeMillis) {
|
||||
_requestTimes.pop_front();
|
||||
}
|
||||
|
||||
_requestTimes.push_back(now);
|
||||
|
||||
|
@ -244,12 +275,12 @@ bool AsyncRateLimitMiddleware::isRequestAllowed(uint32_t& retryAfterSeconds) {
|
|||
return true;
|
||||
}
|
||||
|
||||
void AsyncRateLimitMiddleware::run(AsyncWebServerRequest* request, ArMiddlewareNext next) {
|
||||
void AsyncRateLimitMiddleware::run(AsyncWebServerRequest *request, ArMiddlewareNext next) {
|
||||
uint32_t retryAfterSeconds;
|
||||
if (isRequestAllowed(retryAfterSeconds)) {
|
||||
next();
|
||||
} else {
|
||||
AsyncWebServerResponse* response = request->beginResponse(429);
|
||||
AsyncWebServerResponse *response = request->beginResponse(429);
|
||||
response->addHeader(asyncsrv::T_retry_after, retryAfterSeconds);
|
||||
request->send(response);
|
||||
}
|
||||
|
|
|
@ -1,29 +1,12 @@
|
|||
/*
|
||||
Asynchronous WebServer library for Espressif MCUs
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
|
||||
|
||||
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
|
||||
This file is part of the esp8266 core for Arduino environment.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
#include "WebAuthentication.h"
|
||||
#include <libb64/cencode.h>
|
||||
#if defined(ESP32) || defined(TARGET_RP2040)
|
||||
#include <MD5Builder.h>
|
||||
#if defined(ESP32) || defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
|
||||
#include <MD5Builder.h>
|
||||
#else
|
||||
#include "md5.h"
|
||||
#include "md5.h"
|
||||
#endif
|
||||
#include "literals.h"
|
||||
|
||||
|
@ -31,23 +14,25 @@ using namespace asyncsrv;
|
|||
|
||||
// Basic Auth hash = base64("username:password")
|
||||
|
||||
bool checkBasicAuthentication(const char* hash, const char* username, const char* password) {
|
||||
if (username == NULL || password == NULL || hash == NULL)
|
||||
bool checkBasicAuthentication(const char *hash, const char *username, const char *password) {
|
||||
if (username == NULL || password == NULL || hash == NULL) {
|
||||
return false;
|
||||
}
|
||||
return generateBasicHash(username, password).equalsIgnoreCase(hash);
|
||||
}
|
||||
|
||||
String generateBasicHash(const char* username, const char* password) {
|
||||
if (username == NULL || password == NULL)
|
||||
String generateBasicHash(const char *username, const char *password) {
|
||||
if (username == NULL || password == NULL) {
|
||||
return emptyString;
|
||||
}
|
||||
|
||||
size_t toencodeLen = strlen(username) + strlen(password) + 1;
|
||||
|
||||
char* toencode = new char[toencodeLen + 1];
|
||||
char *toencode = new char[toencodeLen + 1];
|
||||
if (toencode == NULL) {
|
||||
return emptyString;
|
||||
}
|
||||
char* encoded = new char[base64_encode_expected_len(toencodeLen) + 1];
|
||||
char *encoded = new char[base64_encode_expected_len(toencodeLen) + 1];
|
||||
if (encoded == NULL) {
|
||||
delete[] toencode;
|
||||
return emptyString;
|
||||
|
@ -64,8 +49,8 @@ String generateBasicHash(const char* username, const char* password) {
|
|||
return emptyString;
|
||||
}
|
||||
|
||||
static bool getMD5(uint8_t* data, uint16_t len, char* output) { // 33 bytes or more
|
||||
#if defined(ESP32) || defined(TARGET_RP2040)
|
||||
static bool getMD5(uint8_t *data, uint16_t len, char *output) { // 33 bytes or more
|
||||
#if defined(ESP32) || defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
|
||||
MD5Builder md5;
|
||||
md5.begin();
|
||||
md5.add(data, len);
|
||||
|
@ -74,9 +59,10 @@ static bool getMD5(uint8_t* data, uint16_t len, char* output) { // 33 bytes or m
|
|||
#else
|
||||
md5_context_t _ctx;
|
||||
|
||||
uint8_t* _buf = (uint8_t*)malloc(16);
|
||||
if (_buf == NULL)
|
||||
uint8_t *_buf = (uint8_t *)malloc(16);
|
||||
if (_buf == NULL) {
|
||||
return false;
|
||||
}
|
||||
memset(_buf, 0x00, 16);
|
||||
|
||||
MD5Init(&_ctx);
|
||||
|
@ -98,49 +84,77 @@ String genRandomMD5() {
|
|||
#else
|
||||
uint32_t r = rand();
|
||||
#endif
|
||||
char* out = (char*)malloc(33);
|
||||
if (out == NULL || !getMD5((uint8_t*)(&r), 4, out))
|
||||
char *out = (char *)malloc(33);
|
||||
if (out == NULL || !getMD5((uint8_t *)(&r), 4, out)) {
|
||||
#ifdef ESP32
|
||||
log_e("Failed to allocate");
|
||||
#endif
|
||||
return emptyString;
|
||||
}
|
||||
String res = String(out);
|
||||
free(out);
|
||||
return res;
|
||||
}
|
||||
|
||||
static String stringMD5(const String& in) {
|
||||
char* out = (char*)malloc(33);
|
||||
if (out == NULL || !getMD5((uint8_t*)(in.c_str()), in.length(), out))
|
||||
static String stringMD5(const String &in) {
|
||||
char *out = (char *)malloc(33);
|
||||
if (out == NULL || !getMD5((uint8_t *)(in.c_str()), in.length(), out)) {
|
||||
#ifdef ESP32
|
||||
log_e("Failed to allocate");
|
||||
#endif
|
||||
return emptyString;
|
||||
}
|
||||
String res = String(out);
|
||||
free(out);
|
||||
return res;
|
||||
}
|
||||
|
||||
String generateDigestHash(const char* username, const char* password, const char* realm) {
|
||||
String generateDigestHash(const char *username, const char *password, const char *realm) {
|
||||
if (username == NULL || password == NULL || realm == NULL) {
|
||||
return emptyString;
|
||||
}
|
||||
char* out = (char*)malloc(33);
|
||||
char *out = (char *)malloc(33);
|
||||
if (out == NULL) {
|
||||
#ifdef ESP32
|
||||
log_e("Failed to allocate");
|
||||
#endif
|
||||
return emptyString;
|
||||
}
|
||||
|
||||
String in;
|
||||
in.reserve(strlen(username) + strlen(realm) + strlen(password) + 2);
|
||||
if (!in.reserve(strlen(username) + strlen(realm) + strlen(password) + 2)) {
|
||||
#ifdef ESP32
|
||||
log_e("Failed to allocate");
|
||||
#endif
|
||||
free(out);
|
||||
return emptyString;
|
||||
}
|
||||
|
||||
in.concat(username);
|
||||
in.concat(':');
|
||||
in.concat(realm);
|
||||
in.concat(':');
|
||||
in.concat(password);
|
||||
|
||||
if (out == NULL || !getMD5((uint8_t*)(in.c_str()), in.length(), out))
|
||||
if (!getMD5((uint8_t *)(in.c_str()), in.length(), out)) {
|
||||
#ifdef ESP32
|
||||
log_e("Failed to allocate");
|
||||
#endif
|
||||
free(out);
|
||||
return emptyString;
|
||||
}
|
||||
|
||||
in = String(out);
|
||||
free(out);
|
||||
return in;
|
||||
}
|
||||
|
||||
bool checkDigestAuthentication(const char* header, const char* method, const char* username, const char* password, const char* realm, bool passwordIsHash, const char* nonce, const char* opaque, const char* uri)
|
||||
{
|
||||
bool checkDigestAuthentication(
|
||||
const char *header, const char *method, const char *username, const char *password, const char *realm, bool passwordIsHash, const char *nonce,
|
||||
const char *opaque, const char *uri
|
||||
) {
|
||||
if (username == NULL || password == NULL || header == NULL || method == NULL) {
|
||||
// os_printf("AUTH FAIL: missing requred fields\n");
|
||||
// os_printf("AUTH FAIL: missing required fields\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -160,8 +174,8 @@ bool checkDigestAuthentication(const char* header, const char* method, const cha
|
|||
String myNc;
|
||||
String myCnonce;
|
||||
|
||||
myHeader += (char)0x2c; // ','
|
||||
myHeader += (char)0x20; // ' '
|
||||
myHeader += (char)0x2c; // ','
|
||||
myHeader += (char)0x20; // ' '
|
||||
do {
|
||||
String avLine(myHeader.substring(0, nextBreak));
|
||||
avLine.trim();
|
||||
|
|
|
@ -1,37 +1,22 @@
|
|||
/*
|
||||
Asynchronous WebServer library for Espressif MCUs
|
||||
|
||||
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
|
||||
This file is part of the esp8266 core for Arduino environment.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
|
||||
|
||||
#ifndef WEB_AUTHENTICATION_H_
|
||||
#define WEB_AUTHENTICATION_H_
|
||||
|
||||
#include "Arduino.h"
|
||||
|
||||
bool checkBasicAuthentication(const char* header, const char* username, const char* password);
|
||||
bool checkBasicAuthentication(const char *header, const char *username, const char *password);
|
||||
|
||||
bool checkDigestAuthentication(const char* header, const char* method, const char* username, const char* password, const char* realm, bool passwordIsHash, const char* nonce, const char* opaque, const char* uri);
|
||||
bool checkDigestAuthentication(
|
||||
const char *header, const char *method, const char *username, const char *password, const char *realm, bool passwordIsHash, const char *nonce,
|
||||
const char *opaque, const char *uri
|
||||
);
|
||||
|
||||
// for storing hashed versions on the device that can be authenticated against
|
||||
String generateDigestHash(const char* username, const char* password, const char* realm);
|
||||
String generateDigestHash(const char *username, const char *password, const char *realm);
|
||||
|
||||
String generateBasicHash(const char* username, const char* password);
|
||||
String generateBasicHash(const char *username, const char *password);
|
||||
|
||||
String genRandomMD5();
|
||||
|
||||
|
|
|
@ -1,101 +1,94 @@
|
|||
/*
|
||||
Asynchronous WebServer library for Espressif MCUs
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
|
||||
|
||||
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
|
||||
This file is part of the esp8266 core for Arduino environment.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
#ifndef ASYNCWEBSERVERHANDLERIMPL_H_
|
||||
#define ASYNCWEBSERVERHANDLERIMPL_H_
|
||||
|
||||
#include <string>
|
||||
#ifdef ASYNCWEBSERVER_REGEX
|
||||
#include <regex>
|
||||
#include <regex>
|
||||
#endif
|
||||
|
||||
#include "stddef.h"
|
||||
#include <time.h>
|
||||
|
||||
class AsyncStaticWebHandler : public AsyncWebHandler {
|
||||
using File = fs::File;
|
||||
using FS = fs::FS;
|
||||
using File = fs::File;
|
||||
using FS = fs::FS;
|
||||
|
||||
private:
|
||||
bool _getFile(AsyncWebServerRequest* request) const;
|
||||
bool _searchFile(AsyncWebServerRequest* request, const String& path);
|
||||
uint8_t _countBits(const uint8_t value) const;
|
||||
private:
|
||||
bool _getFile(AsyncWebServerRequest *request) const;
|
||||
bool _searchFile(AsyncWebServerRequest *request, const String &path);
|
||||
uint8_t _countBits(const uint8_t value) const;
|
||||
|
||||
protected:
|
||||
FS _fs;
|
||||
String _uri;
|
||||
String _path;
|
||||
String _default_file;
|
||||
String _cache_control;
|
||||
String _last_modified;
|
||||
AwsTemplateProcessor _callback;
|
||||
bool _isDir;
|
||||
bool _tryGzipFirst = true;
|
||||
protected:
|
||||
FS _fs;
|
||||
String _uri;
|
||||
String _path;
|
||||
String _default_file;
|
||||
String _cache_control;
|
||||
String _last_modified;
|
||||
AwsTemplateProcessor _callback;
|
||||
bool _isDir;
|
||||
bool _tryGzipFirst = true;
|
||||
|
||||
public:
|
||||
AsyncStaticWebHandler(const char* uri, FS& fs, const char* path, const char* cache_control);
|
||||
bool canHandle(AsyncWebServerRequest* request) const override final;
|
||||
void handleRequest(AsyncWebServerRequest* request) override final;
|
||||
AsyncStaticWebHandler& setTryGzipFirst(bool value);
|
||||
AsyncStaticWebHandler& setIsDir(bool isDir);
|
||||
AsyncStaticWebHandler& setDefaultFile(const char* filename);
|
||||
AsyncStaticWebHandler& setCacheControl(const char* cache_control);
|
||||
public:
|
||||
AsyncStaticWebHandler(const char *uri, FS &fs, const char *path, const char *cache_control);
|
||||
bool canHandle(AsyncWebServerRequest *request) const override final;
|
||||
void handleRequest(AsyncWebServerRequest *request) override final;
|
||||
AsyncStaticWebHandler &setTryGzipFirst(bool value);
|
||||
AsyncStaticWebHandler &setIsDir(bool isDir);
|
||||
AsyncStaticWebHandler &setDefaultFile(const char *filename);
|
||||
AsyncStaticWebHandler &setCacheControl(const char *cache_control);
|
||||
|
||||
/**
|
||||
/**
|
||||
* @brief Set the Last-Modified time for the object
|
||||
*
|
||||
* @param last_modified
|
||||
* @return AsyncStaticWebHandler&
|
||||
*
|
||||
* @param last_modified
|
||||
* @return AsyncStaticWebHandler&
|
||||
*/
|
||||
AsyncStaticWebHandler& setLastModified(const char* last_modified);
|
||||
AsyncStaticWebHandler& setLastModified(struct tm* last_modified);
|
||||
AsyncStaticWebHandler& setLastModified(time_t last_modified);
|
||||
// sets to current time. Make sure sntp is runing and time is updated
|
||||
AsyncStaticWebHandler& setLastModified();
|
||||
AsyncStaticWebHandler &setLastModified(const char *last_modified);
|
||||
AsyncStaticWebHandler &setLastModified(struct tm *last_modified);
|
||||
AsyncStaticWebHandler &setLastModified(time_t last_modified);
|
||||
// sets to current time. Make sure sntp is running and time is updated
|
||||
AsyncStaticWebHandler &setLastModified();
|
||||
|
||||
AsyncStaticWebHandler& setTemplateProcessor(AwsTemplateProcessor newCallback);
|
||||
AsyncStaticWebHandler &setTemplateProcessor(AwsTemplateProcessor newCallback);
|
||||
};
|
||||
|
||||
class AsyncCallbackWebHandler : public AsyncWebHandler {
|
||||
private:
|
||||
protected:
|
||||
String _uri;
|
||||
WebRequestMethodComposite _method;
|
||||
ArRequestHandlerFunction _onRequest;
|
||||
ArUploadHandlerFunction _onUpload;
|
||||
ArBodyHandlerFunction _onBody;
|
||||
bool _isRegex;
|
||||
private:
|
||||
protected:
|
||||
String _uri;
|
||||
WebRequestMethodComposite _method;
|
||||
ArRequestHandlerFunction _onRequest;
|
||||
ArUploadHandlerFunction _onUpload;
|
||||
ArBodyHandlerFunction _onBody;
|
||||
bool _isRegex;
|
||||
|
||||
public:
|
||||
AsyncCallbackWebHandler() : _uri(), _method(HTTP_ANY), _onRequest(NULL), _onUpload(NULL), _onBody(NULL), _isRegex(false) {}
|
||||
void setUri(const String& uri);
|
||||
void setMethod(WebRequestMethodComposite method) { _method = method; }
|
||||
void onRequest(ArRequestHandlerFunction fn) { _onRequest = fn; }
|
||||
void onUpload(ArUploadHandlerFunction fn) { _onUpload = fn; }
|
||||
void onBody(ArBodyHandlerFunction fn) { _onBody = fn; }
|
||||
public:
|
||||
AsyncCallbackWebHandler() : _uri(), _method(HTTP_ANY), _onRequest(NULL), _onUpload(NULL), _onBody(NULL), _isRegex(false) {}
|
||||
void setUri(const String &uri);
|
||||
void setMethod(WebRequestMethodComposite method) {
|
||||
_method = method;
|
||||
}
|
||||
void onRequest(ArRequestHandlerFunction fn) {
|
||||
_onRequest = fn;
|
||||
}
|
||||
void onUpload(ArUploadHandlerFunction fn) {
|
||||
_onUpload = fn;
|
||||
}
|
||||
void onBody(ArBodyHandlerFunction fn) {
|
||||
_onBody = fn;
|
||||
}
|
||||
|
||||
bool canHandle(AsyncWebServerRequest* request) const override final;
|
||||
void handleRequest(AsyncWebServerRequest* request) override final;
|
||||
void handleUpload(AsyncWebServerRequest* request, const String& filename, size_t index, uint8_t* data, size_t len, bool final) override final;
|
||||
void handleBody(AsyncWebServerRequest* request, uint8_t* data, size_t len, size_t index, size_t total) override final;
|
||||
bool isRequestHandlerTrivial() const override final { return !_onRequest; }
|
||||
bool canHandle(AsyncWebServerRequest *request) const override final;
|
||||
void handleRequest(AsyncWebServerRequest *request) override final;
|
||||
void handleUpload(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final) override final;
|
||||
void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override final;
|
||||
bool isRequestHandlerTrivial() const override final {
|
||||
return !_onRequest;
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* ASYNCWEBSERVERHANDLERIMPL_H_ */
|
||||
|
|
|
@ -1,33 +1,16 @@
|
|||
/*
|
||||
Asynchronous WebServer library for Espressif MCUs
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
|
||||
|
||||
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
|
||||
This file is part of the esp8266 core for Arduino environment.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
#include "ESPAsyncWebServer.h"
|
||||
#include "WebHandlerImpl.h"
|
||||
|
||||
using namespace asyncsrv;
|
||||
|
||||
AsyncWebHandler& AsyncWebHandler::setFilter(ArRequestFilterFunction fn) {
|
||||
AsyncWebHandler &AsyncWebHandler::setFilter(ArRequestFilterFunction fn) {
|
||||
_filter = fn;
|
||||
return *this;
|
||||
}
|
||||
AsyncWebHandler& AsyncWebHandler::setAuthentication(const char* username, const char* password, AsyncAuthType authMethod) {
|
||||
AsyncWebHandler &AsyncWebHandler::setAuthentication(const char *username, const char *password, AsyncAuthType authMethod) {
|
||||
if (!_authMiddleware) {
|
||||
_authMiddleware = new AsyncAuthenticationMiddleware();
|
||||
_authMiddleware->_freeOnRemoval = true;
|
||||
|
@ -39,13 +22,15 @@ AsyncWebHandler& AsyncWebHandler::setAuthentication(const char* username, const
|
|||
return *this;
|
||||
};
|
||||
|
||||
AsyncStaticWebHandler::AsyncStaticWebHandler(const char* uri, FS& fs, const char* path, const char* cache_control)
|
||||
: _fs(fs), _uri(uri), _path(path), _default_file(F("index.htm")), _cache_control(cache_control), _last_modified(), _callback(nullptr) {
|
||||
AsyncStaticWebHandler::AsyncStaticWebHandler(const char *uri, FS &fs, const char *path, const char *cache_control)
|
||||
: _fs(fs), _uri(uri), _path(path), _default_file(F("index.htm")), _cache_control(cache_control), _last_modified(), _callback(nullptr) {
|
||||
// Ensure leading '/'
|
||||
if (_uri.length() == 0 || _uri[0] != '/')
|
||||
if (_uri.length() == 0 || _uri[0] != '/') {
|
||||
_uri = String('/') + _uri;
|
||||
if (_path.length() == 0 || _path[0] != '/')
|
||||
}
|
||||
if (_path.length() == 0 || _path[0] != '/') {
|
||||
_path = String('/') + _path;
|
||||
}
|
||||
|
||||
// If path ends with '/' we assume a hint that this is a directory to improve performance.
|
||||
// However - if it does not end with '/' we, can't assume a file, path can still be a directory.
|
||||
|
@ -53,45 +38,47 @@ AsyncStaticWebHandler::AsyncStaticWebHandler(const char* uri, FS& fs, const char
|
|||
|
||||
// Remove the trailing '/' so we can handle default file
|
||||
// Notice that root will be "" not "/"
|
||||
if (_uri[_uri.length() - 1] == '/')
|
||||
if (_uri[_uri.length() - 1] == '/') {
|
||||
_uri = _uri.substring(0, _uri.length() - 1);
|
||||
if (_path[_path.length() - 1] == '/')
|
||||
}
|
||||
if (_path[_path.length() - 1] == '/') {
|
||||
_path = _path.substring(0, _path.length() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
AsyncStaticWebHandler& AsyncStaticWebHandler::setTryGzipFirst(bool value) {
|
||||
AsyncStaticWebHandler &AsyncStaticWebHandler::setTryGzipFirst(bool value) {
|
||||
_tryGzipFirst = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
AsyncStaticWebHandler& AsyncStaticWebHandler::setIsDir(bool isDir) {
|
||||
AsyncStaticWebHandler &AsyncStaticWebHandler::setIsDir(bool isDir) {
|
||||
_isDir = isDir;
|
||||
return *this;
|
||||
}
|
||||
|
||||
AsyncStaticWebHandler& AsyncStaticWebHandler::setDefaultFile(const char* filename) {
|
||||
AsyncStaticWebHandler &AsyncStaticWebHandler::setDefaultFile(const char *filename) {
|
||||
_default_file = filename;
|
||||
return *this;
|
||||
}
|
||||
|
||||
AsyncStaticWebHandler& AsyncStaticWebHandler::setCacheControl(const char* cache_control) {
|
||||
AsyncStaticWebHandler &AsyncStaticWebHandler::setCacheControl(const char *cache_control) {
|
||||
_cache_control = cache_control;
|
||||
return *this;
|
||||
}
|
||||
|
||||
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(const char* last_modified) {
|
||||
AsyncStaticWebHandler &AsyncStaticWebHandler::setLastModified(const char *last_modified) {
|
||||
_last_modified = last_modified;
|
||||
return *this;
|
||||
}
|
||||
|
||||
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(struct tm* last_modified) {
|
||||
AsyncStaticWebHandler &AsyncStaticWebHandler::setLastModified(struct tm *last_modified) {
|
||||
char result[30];
|
||||
#ifdef ESP8266
|
||||
auto formatP = PSTR("%a, %d %b %Y %H:%M:%S GMT");
|
||||
char format[strlen_P(formatP) + 1];
|
||||
strcpy_P(format, formatP);
|
||||
#else
|
||||
static constexpr const char* format = "%a, %d %b %Y %H:%M:%S GMT";
|
||||
static constexpr const char *format = "%a, %d %b %Y %H:%M:%S GMT";
|
||||
#endif
|
||||
|
||||
strftime(result, sizeof(result), format, last_modified);
|
||||
|
@ -99,22 +86,23 @@ AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(struct tm* last_mo
|
|||
return *this;
|
||||
}
|
||||
|
||||
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(time_t last_modified) {
|
||||
return setLastModified((struct tm*)gmtime(&last_modified));
|
||||
AsyncStaticWebHandler &AsyncStaticWebHandler::setLastModified(time_t last_modified) {
|
||||
return setLastModified((struct tm *)gmtime(&last_modified));
|
||||
}
|
||||
|
||||
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified() {
|
||||
AsyncStaticWebHandler &AsyncStaticWebHandler::setLastModified() {
|
||||
time_t last_modified;
|
||||
if (time(&last_modified) == 0) // time is not yet set
|
||||
if (time(&last_modified) == 0) { // time is not yet set
|
||||
return *this;
|
||||
}
|
||||
return setLastModified(last_modified);
|
||||
}
|
||||
|
||||
bool AsyncStaticWebHandler::canHandle(AsyncWebServerRequest* request) const {
|
||||
bool AsyncStaticWebHandler::canHandle(AsyncWebServerRequest *request) const {
|
||||
return request->isHTTP() && request->method() == HTTP_GET && request->url().startsWith(_uri) && _getFile(request);
|
||||
}
|
||||
|
||||
bool AsyncStaticWebHandler::_getFile(AsyncWebServerRequest* request) const {
|
||||
bool AsyncStaticWebHandler::_getFile(AsyncWebServerRequest *request) const {
|
||||
// Remove the found uri
|
||||
String path = request->url().substring(_uri.length());
|
||||
|
||||
|
@ -124,28 +112,31 @@ bool AsyncStaticWebHandler::_getFile(AsyncWebServerRequest* request) const {
|
|||
path = _path + path;
|
||||
|
||||
// Do we have a file or .gz file
|
||||
if (!canSkipFileCheck && const_cast<AsyncStaticWebHandler*>(this)->_searchFile(request, path))
|
||||
if (!canSkipFileCheck && const_cast<AsyncStaticWebHandler *>(this)->_searchFile(request, path)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Can't handle if not default file
|
||||
if (_default_file.length() == 0)
|
||||
if (_default_file.length() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try to add default file, ensure there is a trailing '/' ot the path.
|
||||
if (path.length() == 0 || path[path.length() - 1] != '/')
|
||||
// Try to add default file, ensure there is a trailing '/' to the path.
|
||||
if (path.length() == 0 || path[path.length() - 1] != '/') {
|
||||
path += String('/');
|
||||
}
|
||||
path += _default_file;
|
||||
|
||||
return const_cast<AsyncStaticWebHandler*>(this)->_searchFile(request, path);
|
||||
return const_cast<AsyncStaticWebHandler *>(this)->_searchFile(request, path);
|
||||
}
|
||||
|
||||
#ifdef ESP32
|
||||
#define FILE_IS_REAL(f) (f == true && !f.isDirectory())
|
||||
#define FILE_IS_REAL(f) (f == true && !f.isDirectory())
|
||||
#else
|
||||
#define FILE_IS_REAL(f) (f == true)
|
||||
#define FILE_IS_REAL(f) (f == true)
|
||||
#endif
|
||||
|
||||
bool AsyncStaticWebHandler::_searchFile(AsyncWebServerRequest* request, const String& path) {
|
||||
bool AsyncStaticWebHandler::_searchFile(AsyncWebServerRequest *request, const String &path) {
|
||||
bool fileFound = false;
|
||||
bool gzipFound = false;
|
||||
|
||||
|
@ -180,9 +171,17 @@ bool AsyncStaticWebHandler::_searchFile(AsyncWebServerRequest* request, const St
|
|||
if (found) {
|
||||
// Extract the file name from the path and keep it in _tempObject
|
||||
size_t pathLen = path.length();
|
||||
char* _tempPath = (char*)malloc(pathLen + 1);
|
||||
char *_tempPath = (char *)malloc(pathLen + 1);
|
||||
if (_tempPath == NULL) {
|
||||
#ifdef ESP32
|
||||
log_e("Failed to allocate");
|
||||
#endif
|
||||
request->abort();
|
||||
request->_tempFile.close();
|
||||
return false;
|
||||
}
|
||||
snprintf_P(_tempPath, pathLen + 1, PSTR("%s"), path.c_str());
|
||||
request->_tempObject = (void*)_tempPath;
|
||||
request->_tempObject = (void *)_tempPath;
|
||||
}
|
||||
|
||||
return found;
|
||||
|
@ -191,81 +190,97 @@ bool AsyncStaticWebHandler::_searchFile(AsyncWebServerRequest* request, const St
|
|||
uint8_t AsyncStaticWebHandler::_countBits(const uint8_t value) const {
|
||||
uint8_t w = value;
|
||||
uint8_t n;
|
||||
for (n = 0; w != 0; n++)
|
||||
for (n = 0; w != 0; n++) {
|
||||
w &= w - 1;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest* request) {
|
||||
void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest *request) {
|
||||
// Get the filename from request->_tempObject and free it
|
||||
String filename((char*)request->_tempObject);
|
||||
String filename((char *)request->_tempObject);
|
||||
free(request->_tempObject);
|
||||
request->_tempObject = NULL;
|
||||
|
||||
if (request->_tempFile != true){
|
||||
if (request->_tempFile != true) {
|
||||
request->send(404);
|
||||
return;
|
||||
}
|
||||
|
||||
time_t lw = request->_tempFile.getLastWrite(); // get last file mod time (if supported by FS)
|
||||
// set etag to lastmod timestamp if available, otherwise to size
|
||||
String etag;
|
||||
if (lw) {
|
||||
setLastModified(lw);
|
||||
#if defined(TARGET_RP2040)
|
||||
// time_t == long long int
|
||||
constexpr size_t len = 1 + 8 * sizeof(time_t);
|
||||
char buf[len];
|
||||
char* ret = lltoa(lw ^ request->_tempFile.size(), buf, len, 10);
|
||||
etag = ret ? String(ret) : String(request->_tempFile.size());
|
||||
time_t lw = request->_tempFile.getLastWrite(); // get last file mod time (if supported by FS)
|
||||
// set etag to lastmod timestamp if available, otherwise to size
|
||||
String etag;
|
||||
if (lw) {
|
||||
setLastModified(lw);
|
||||
#if defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
|
||||
// time_t == long long int
|
||||
constexpr size_t len = 1 + 8 * sizeof(time_t);
|
||||
char buf[len];
|
||||
char *ret = lltoa(lw ^ request->_tempFile.size(), buf, len, 10);
|
||||
etag = ret ? String(ret) : String(request->_tempFile.size());
|
||||
#else
|
||||
etag = lw ^ request->_tempFile.size(); // etag combines file size and lastmod timestamp
|
||||
etag = lw ^ request->_tempFile.size(); // etag combines file size and lastmod timestamp
|
||||
#endif
|
||||
} else {
|
||||
etag = request->_tempFile.size();
|
||||
}
|
||||
} else {
|
||||
#if defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
|
||||
etag = String(request->_tempFile.size());
|
||||
#else
|
||||
etag = request->_tempFile.size();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool not_modified = false;
|
||||
bool not_modified = false;
|
||||
|
||||
// if-none-match has precedence over if-modified-since
|
||||
if (request->hasHeader(T_INM))
|
||||
not_modified = request->header(T_INM).equals(etag);
|
||||
else if (_last_modified.length())
|
||||
not_modified = request->header(T_IMS).equals(_last_modified);
|
||||
// if-none-match has precedence over if-modified-since
|
||||
if (request->hasHeader(T_INM)) {
|
||||
not_modified = request->header(T_INM).equals(etag);
|
||||
} else if (_last_modified.length()) {
|
||||
not_modified = request->header(T_IMS).equals(_last_modified);
|
||||
}
|
||||
|
||||
AsyncWebServerResponse* response;
|
||||
AsyncWebServerResponse *response;
|
||||
|
||||
if (not_modified){
|
||||
request->_tempFile.close();
|
||||
response = new AsyncBasicResponse(304); // Not modified
|
||||
} else {
|
||||
response = new AsyncFileResponse(request->_tempFile, filename, emptyString, false, _callback);
|
||||
}
|
||||
if (not_modified) {
|
||||
request->_tempFile.close();
|
||||
response = new AsyncBasicResponse(304); // Not modified
|
||||
} else {
|
||||
response = new AsyncFileResponse(request->_tempFile, filename, emptyString, false, _callback);
|
||||
}
|
||||
|
||||
response->addHeader(T_ETag, etag.c_str());
|
||||
if (!response) {
|
||||
#ifdef ESP32
|
||||
log_e("Failed to allocate");
|
||||
#endif
|
||||
request->abort();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_last_modified.length())
|
||||
response->addHeader(T_Last_Modified, _last_modified.c_str());
|
||||
if (_cache_control.length())
|
||||
response->addHeader(T_Cache_Control, _cache_control.c_str());
|
||||
|
||||
request->send(response);
|
||||
response->addHeader(T_ETag, etag.c_str());
|
||||
|
||||
if (_last_modified.length()) {
|
||||
response->addHeader(T_Last_Modified, _last_modified.c_str());
|
||||
}
|
||||
if (_cache_control.length()) {
|
||||
response->addHeader(T_Cache_Control, _cache_control.c_str());
|
||||
}
|
||||
|
||||
request->send(response);
|
||||
}
|
||||
|
||||
AsyncStaticWebHandler& AsyncStaticWebHandler::setTemplateProcessor(AwsTemplateProcessor newCallback) {
|
||||
AsyncStaticWebHandler &AsyncStaticWebHandler::setTemplateProcessor(AwsTemplateProcessor newCallback) {
|
||||
_callback = newCallback;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void AsyncCallbackWebHandler::setUri(const String& uri) {
|
||||
void AsyncCallbackWebHandler::setUri(const String &uri) {
|
||||
_uri = uri;
|
||||
_isRegex = uri.startsWith("^") && uri.endsWith("$");
|
||||
}
|
||||
|
||||
bool AsyncCallbackWebHandler::canHandle(AsyncWebServerRequest* request) const {
|
||||
if (!_onRequest || !request->isHTTP() || !(_method & request->method()))
|
||||
bool AsyncCallbackWebHandler::canHandle(AsyncWebServerRequest *request) const {
|
||||
if (!_onRequest || !request->isHTTP() || !(_method & request->method())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef ASYNCWEBSERVER_REGEX
|
||||
if (_isRegex) {
|
||||
|
@ -273,7 +288,7 @@ bool AsyncCallbackWebHandler::canHandle(AsyncWebServerRequest* request) const {
|
|||
std::smatch matches;
|
||||
std::string s(request->url().c_str());
|
||||
if (std::regex_search(s, matches, pattern)) {
|
||||
for (size_t i = 1; i < matches.size(); ++i) { // start from 1
|
||||
for (size_t i = 1; i < matches.size(); ++i) { // start from 1
|
||||
request->_addPathParam(matches[i].str().c_str());
|
||||
}
|
||||
} else {
|
||||
|
@ -284,31 +299,37 @@ bool AsyncCallbackWebHandler::canHandle(AsyncWebServerRequest* request) const {
|
|||
if (_uri.length() && _uri.startsWith("/*.")) {
|
||||
String uriTemplate = String(_uri);
|
||||
uriTemplate = uriTemplate.substring(uriTemplate.lastIndexOf("."));
|
||||
if (!request->url().endsWith(uriTemplate))
|
||||
if (!request->url().endsWith(uriTemplate)) {
|
||||
return false;
|
||||
}
|
||||
} else if (_uri.length() && _uri.endsWith("*")) {
|
||||
String uriTemplate = String(_uri);
|
||||
uriTemplate = uriTemplate.substring(0, uriTemplate.length() - 1);
|
||||
if (!request->url().startsWith(uriTemplate))
|
||||
if (!request->url().startsWith(uriTemplate)) {
|
||||
return false;
|
||||
} else if (_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri + "/")))
|
||||
}
|
||||
} else if (_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri + "/"))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void AsyncCallbackWebHandler::handleRequest(AsyncWebServerRequest* request) {
|
||||
if (_onRequest)
|
||||
void AsyncCallbackWebHandler::handleRequest(AsyncWebServerRequest *request) {
|
||||
if (_onRequest) {
|
||||
_onRequest(request);
|
||||
else
|
||||
request->send(500);
|
||||
} else {
|
||||
request->send(404, T_text_plain, "Not found");
|
||||
}
|
||||
}
|
||||
void AsyncCallbackWebHandler::handleUpload(AsyncWebServerRequest* request, const String& filename, size_t index, uint8_t* data, size_t len, bool final) {
|
||||
if (_onUpload)
|
||||
void AsyncCallbackWebHandler::handleUpload(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final) {
|
||||
if (_onUpload) {
|
||||
_onUpload(request, filename, index, data, len, final);
|
||||
}
|
||||
}
|
||||
void AsyncCallbackWebHandler::handleBody(AsyncWebServerRequest* request, uint8_t* data, size_t len, size_t index, size_t total) {
|
||||
void AsyncCallbackWebHandler::handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
|
||||
// ESP_LOGD("AsyncWebServer", "AsyncCallbackWebHandler::handleBody");
|
||||
if (_onBody)
|
||||
if (_onBody) {
|
||||
_onBody(request, data, len, index, total);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,30 +1,13 @@
|
|||
/*
|
||||
Asynchronous WebServer library for Espressif MCUs
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
|
||||
|
||||
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
|
||||
This file is part of the esp8266 core for Arduino environment.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
#ifndef ASYNCWEBSERVERRESPONSEIMPL_H_
|
||||
#define ASYNCWEBSERVERRESPONSEIMPL_H_
|
||||
|
||||
#ifdef Arduino_h
|
||||
// arduino is not compatible with std::vector
|
||||
#undef min
|
||||
#undef max
|
||||
// arduino is not compatible with std::vector
|
||||
#undef min
|
||||
#undef max
|
||||
#endif
|
||||
#include "literals.h"
|
||||
#include <StreamString.h>
|
||||
|
@ -34,129 +17,158 @@
|
|||
// It is possible to restore these defines, but one can use _min and _max instead. Or std::min, std::max.
|
||||
|
||||
class AsyncBasicResponse : public AsyncWebServerResponse {
|
||||
private:
|
||||
String _content;
|
||||
private:
|
||||
String _content;
|
||||
|
||||
public:
|
||||
explicit AsyncBasicResponse(int code, const char* contentType = asyncsrv::empty, const char* content = asyncsrv::empty);
|
||||
AsyncBasicResponse(int code, const String& contentType, const String& content = emptyString) : AsyncBasicResponse(code, contentType.c_str(), content.c_str()) {}
|
||||
void _respond(AsyncWebServerRequest* request) override final;
|
||||
size_t _ack(AsyncWebServerRequest* request, size_t len, uint32_t time) override final;
|
||||
bool _sourceValid() const override final { return true; }
|
||||
public:
|
||||
explicit AsyncBasicResponse(int code, const char *contentType = asyncsrv::empty, const char *content = asyncsrv::empty);
|
||||
AsyncBasicResponse(int code, const String &contentType, const String &content = emptyString)
|
||||
: AsyncBasicResponse(code, contentType.c_str(), content.c_str()) {}
|
||||
void _respond(AsyncWebServerRequest *request) override final;
|
||||
size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time) override final;
|
||||
bool _sourceValid() const override final {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class AsyncAbstractResponse : public AsyncWebServerResponse {
|
||||
private:
|
||||
private:
|
||||
#if ASYNCWEBSERVER_USE_CHUNK_INFLIGHT
|
||||
// amount of responce data in-flight, i.e. sent, but not acked yet
|
||||
size_t _in_flight{0};
|
||||
// in-flight queue credits
|
||||
size_t _in_flight_credit{2};
|
||||
// amount of response data in-flight, i.e. sent, but not acked yet
|
||||
size_t _in_flight{0};
|
||||
// in-flight queue credits
|
||||
size_t _in_flight_credit{2};
|
||||
#endif
|
||||
String _head;
|
||||
// Data is inserted into cache at begin().
|
||||
// This is inefficient with vector, but if we use some other container,
|
||||
// we won't be able to access it as contiguous array of bytes when reading from it,
|
||||
// so by gaining performance in one place, we'll lose it in another.
|
||||
std::vector<uint8_t> _cache;
|
||||
size_t _readDataFromCacheOrContent(uint8_t* data, const size_t len);
|
||||
size_t _fillBufferAndProcessTemplates(uint8_t* buf, size_t maxLen);
|
||||
String _head;
|
||||
// Data is inserted into cache at begin().
|
||||
// This is inefficient with vector, but if we use some other container,
|
||||
// we won't be able to access it as contiguous array of bytes when reading from it,
|
||||
// so by gaining performance in one place, we'll lose it in another.
|
||||
std::vector<uint8_t> _cache;
|
||||
size_t _readDataFromCacheOrContent(uint8_t *data, const size_t len);
|
||||
size_t _fillBufferAndProcessTemplates(uint8_t *buf, size_t maxLen);
|
||||
|
||||
protected:
|
||||
AwsTemplateProcessor _callback;
|
||||
protected:
|
||||
AwsTemplateProcessor _callback;
|
||||
|
||||
public:
|
||||
AsyncAbstractResponse(AwsTemplateProcessor callback = nullptr);
|
||||
virtual ~AsyncAbstractResponse() {}
|
||||
void _respond(AsyncWebServerRequest* request) override final;
|
||||
size_t _ack(AsyncWebServerRequest* request, size_t len, uint32_t time) override final;
|
||||
virtual bool _sourceValid() const { return false; }
|
||||
virtual size_t _fillBuffer(uint8_t* buf __attribute__((unused)), size_t maxLen __attribute__((unused))) { return 0; }
|
||||
public:
|
||||
AsyncAbstractResponse(AwsTemplateProcessor callback = nullptr);
|
||||
virtual ~AsyncAbstractResponse() {}
|
||||
void _respond(AsyncWebServerRequest *request) override final;
|
||||
size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time) override final;
|
||||
virtual bool _sourceValid() const {
|
||||
return false;
|
||||
}
|
||||
virtual size_t _fillBuffer(uint8_t *buf __attribute__((unused)), size_t maxLen __attribute__((unused))) {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
#ifndef TEMPLATE_PLACEHOLDER
|
||||
#define TEMPLATE_PLACEHOLDER '%'
|
||||
#define TEMPLATE_PLACEHOLDER '%'
|
||||
#endif
|
||||
|
||||
#define TEMPLATE_PARAM_NAME_LENGTH 32
|
||||
class AsyncFileResponse : public AsyncAbstractResponse {
|
||||
using File = fs::File;
|
||||
using FS = fs::FS;
|
||||
using File = fs::File;
|
||||
using FS = fs::FS;
|
||||
|
||||
private:
|
||||
File _content;
|
||||
String _path;
|
||||
void _setContentTypeFromPath(const String& path);
|
||||
private:
|
||||
File _content;
|
||||
String _path;
|
||||
void _setContentTypeFromPath(const String &path);
|
||||
|
||||
public:
|
||||
AsyncFileResponse(FS& fs, const String& path, const char* contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr);
|
||||
AsyncFileResponse(FS& fs, const String& path, const String& contentType, bool download = false, AwsTemplateProcessor callback = nullptr) : AsyncFileResponse(fs, path, contentType.c_str(), download, callback) {}
|
||||
AsyncFileResponse(File content, const String& path, const char* contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr);
|
||||
AsyncFileResponse(File content, const String& path, const String& contentType, bool download = false, AwsTemplateProcessor callack = nullptr) : AsyncFileResponse(content, path, contentType.c_str(), download, callack) {}
|
||||
~AsyncFileResponse() { _content.close(); }
|
||||
bool _sourceValid() const override final { return !!(_content); }
|
||||
size_t _fillBuffer(uint8_t* buf, size_t maxLen) override final;
|
||||
public:
|
||||
AsyncFileResponse(FS &fs, const String &path, const char *contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr);
|
||||
AsyncFileResponse(FS &fs, const String &path, const String &contentType, bool download = false, AwsTemplateProcessor callback = nullptr)
|
||||
: AsyncFileResponse(fs, path, contentType.c_str(), download, callback) {}
|
||||
AsyncFileResponse(
|
||||
File content, const String &path, const char *contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr
|
||||
);
|
||||
AsyncFileResponse(File content, const String &path, const String &contentType, bool download = false, AwsTemplateProcessor callback = nullptr)
|
||||
: AsyncFileResponse(content, path, contentType.c_str(), download, callback) {}
|
||||
~AsyncFileResponse() {
|
||||
_content.close();
|
||||
}
|
||||
bool _sourceValid() const override final {
|
||||
return !!(_content);
|
||||
}
|
||||
size_t _fillBuffer(uint8_t *buf, size_t maxLen) override final;
|
||||
};
|
||||
|
||||
class AsyncStreamResponse : public AsyncAbstractResponse {
|
||||
private:
|
||||
Stream* _content;
|
||||
private:
|
||||
Stream *_content;
|
||||
|
||||
public:
|
||||
AsyncStreamResponse(Stream& stream, const char* contentType, size_t len, AwsTemplateProcessor callback = nullptr);
|
||||
AsyncStreamResponse(Stream& stream, const String& contentType, size_t len, AwsTemplateProcessor callback = nullptr) : AsyncStreamResponse(stream, contentType.c_str(), len, callback) {}
|
||||
bool _sourceValid() const override final { return !!(_content); }
|
||||
size_t _fillBuffer(uint8_t* buf, size_t maxLen) override final;
|
||||
public:
|
||||
AsyncStreamResponse(Stream &stream, const char *contentType, size_t len, AwsTemplateProcessor callback = nullptr);
|
||||
AsyncStreamResponse(Stream &stream, const String &contentType, size_t len, AwsTemplateProcessor callback = nullptr)
|
||||
: AsyncStreamResponse(stream, contentType.c_str(), len, callback) {}
|
||||
bool _sourceValid() const override final {
|
||||
return !!(_content);
|
||||
}
|
||||
size_t _fillBuffer(uint8_t *buf, size_t maxLen) override final;
|
||||
};
|
||||
|
||||
class AsyncCallbackResponse : public AsyncAbstractResponse {
|
||||
private:
|
||||
AwsResponseFiller _content;
|
||||
size_t _filledLength;
|
||||
private:
|
||||
AwsResponseFiller _content;
|
||||
size_t _filledLength;
|
||||
|
||||
public:
|
||||
AsyncCallbackResponse(const char* contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr);
|
||||
AsyncCallbackResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) : AsyncCallbackResponse(contentType.c_str(), len, callback, templateCallback) {}
|
||||
bool _sourceValid() const override final { return !!(_content); }
|
||||
size_t _fillBuffer(uint8_t* buf, size_t maxLen) override final;
|
||||
public:
|
||||
AsyncCallbackResponse(const char *contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr);
|
||||
AsyncCallbackResponse(const String &contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr)
|
||||
: AsyncCallbackResponse(contentType.c_str(), len, callback, templateCallback) {}
|
||||
bool _sourceValid() const override final {
|
||||
return !!(_content);
|
||||
}
|
||||
size_t _fillBuffer(uint8_t *buf, size_t maxLen) override final;
|
||||
};
|
||||
|
||||
class AsyncChunkedResponse : public AsyncAbstractResponse {
|
||||
private:
|
||||
AwsResponseFiller _content;
|
||||
size_t _filledLength;
|
||||
private:
|
||||
AwsResponseFiller _content;
|
||||
size_t _filledLength;
|
||||
|
||||
public:
|
||||
AsyncChunkedResponse(const char* contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr);
|
||||
AsyncChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) : AsyncChunkedResponse(contentType.c_str(), callback, templateCallback) {}
|
||||
bool _sourceValid() const override final { return !!(_content); }
|
||||
size_t _fillBuffer(uint8_t* buf, size_t maxLen) override final;
|
||||
public:
|
||||
AsyncChunkedResponse(const char *contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr);
|
||||
AsyncChunkedResponse(const String &contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr)
|
||||
: AsyncChunkedResponse(contentType.c_str(), callback, templateCallback) {}
|
||||
bool _sourceValid() const override final {
|
||||
return !!(_content);
|
||||
}
|
||||
size_t _fillBuffer(uint8_t *buf, size_t maxLen) override final;
|
||||
};
|
||||
|
||||
class AsyncProgmemResponse : public AsyncAbstractResponse {
|
||||
private:
|
||||
const uint8_t* _content;
|
||||
size_t _readLength;
|
||||
private:
|
||||
const uint8_t *_content;
|
||||
size_t _readLength;
|
||||
|
||||
public:
|
||||
AsyncProgmemResponse(int code, const char* contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr);
|
||||
AsyncProgmemResponse(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr) : AsyncProgmemResponse(code, contentType.c_str(), content, len, callback) {}
|
||||
bool _sourceValid() const override final { return true; }
|
||||
size_t _fillBuffer(uint8_t* buf, size_t maxLen) override final;
|
||||
public:
|
||||
AsyncProgmemResponse(int code, const char *contentType, const uint8_t *content, size_t len, AwsTemplateProcessor callback = nullptr);
|
||||
AsyncProgmemResponse(int code, const String &contentType, const uint8_t *content, size_t len, AwsTemplateProcessor callback = nullptr)
|
||||
: AsyncProgmemResponse(code, contentType.c_str(), content, len, callback) {}
|
||||
bool _sourceValid() const override final {
|
||||
return true;
|
||||
}
|
||||
size_t _fillBuffer(uint8_t *buf, size_t maxLen) override final;
|
||||
};
|
||||
|
||||
class AsyncResponseStream : public AsyncAbstractResponse, public Print {
|
||||
private:
|
||||
StreamString _content;
|
||||
private:
|
||||
StreamString _content;
|
||||
|
||||
public:
|
||||
AsyncResponseStream(const char* contentType, size_t bufferSize);
|
||||
AsyncResponseStream(const String& contentType, size_t bufferSize) : AsyncResponseStream(contentType.c_str(), bufferSize) {}
|
||||
bool _sourceValid() const override final { return (_state < RESPONSE_END); }
|
||||
size_t _fillBuffer(uint8_t* buf, size_t maxLen) override final;
|
||||
size_t write(const uint8_t* data, size_t len);
|
||||
size_t write(uint8_t data);
|
||||
using Print::write;
|
||||
public:
|
||||
AsyncResponseStream(const char *contentType, size_t bufferSize);
|
||||
AsyncResponseStream(const String &contentType, size_t bufferSize) : AsyncResponseStream(contentType.c_str(), bufferSize) {}
|
||||
bool _sourceValid() const override final {
|
||||
return (_state < RESPONSE_END);
|
||||
}
|
||||
size_t _fillBuffer(uint8_t *buf, size_t maxLen) override final;
|
||||
size_t write(const uint8_t *data, size_t len);
|
||||
size_t write(uint8_t data);
|
||||
using Print::write;
|
||||
};
|
||||
|
||||
#endif /* ASYNCWEBSERVERRESPONSEIMPL_H_ */
|
||||
|
|
|
@ -1,34 +1,19 @@
|
|||
/*
|
||||
Asynchronous WebServer library for Espressif MCUs
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
|
||||
|
||||
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
|
||||
This file is part of the esp8266 core for Arduino environment.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
#include "ESPAsyncWebServer.h"
|
||||
#include "WebResponseImpl.h"
|
||||
|
||||
using namespace asyncsrv;
|
||||
|
||||
// Since ESP8266 does not link memchr by default, here's its implementation.
|
||||
void* memchr(void* ptr, int ch, size_t count) {
|
||||
unsigned char* p = static_cast<unsigned char*>(ptr);
|
||||
while (count--)
|
||||
if (*p++ == static_cast<unsigned char>(ch))
|
||||
void *memchr(void *ptr, int ch, size_t count) {
|
||||
unsigned char *p = static_cast<unsigned char *>(ptr);
|
||||
while (count--) {
|
||||
if (*p++ == static_cast<unsigned char>(ch)) {
|
||||
return --p;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
@ -37,120 +22,95 @@ void* memchr(void* ptr, int ch, size_t count) {
|
|||
*
|
||||
*/
|
||||
|
||||
const char* AsyncWebServerResponse::responseCodeToString(int code) {
|
||||
const char *AsyncWebServerResponse::responseCodeToString(int code) {
|
||||
switch (code) {
|
||||
case 100:
|
||||
return T_HTTP_CODE_100;
|
||||
case 101:
|
||||
return T_HTTP_CODE_101;
|
||||
case 200:
|
||||
return T_HTTP_CODE_200;
|
||||
case 201:
|
||||
return T_HTTP_CODE_201;
|
||||
case 202:
|
||||
return T_HTTP_CODE_202;
|
||||
case 203:
|
||||
return T_HTTP_CODE_203;
|
||||
case 204:
|
||||
return T_HTTP_CODE_204;
|
||||
case 205:
|
||||
return T_HTTP_CODE_205;
|
||||
case 206:
|
||||
return T_HTTP_CODE_206;
|
||||
case 300:
|
||||
return T_HTTP_CODE_300;
|
||||
case 301:
|
||||
return T_HTTP_CODE_301;
|
||||
case 302:
|
||||
return T_HTTP_CODE_302;
|
||||
case 303:
|
||||
return T_HTTP_CODE_303;
|
||||
case 304:
|
||||
return T_HTTP_CODE_304;
|
||||
case 305:
|
||||
return T_HTTP_CODE_305;
|
||||
case 307:
|
||||
return T_HTTP_CODE_307;
|
||||
case 400:
|
||||
return T_HTTP_CODE_400;
|
||||
case 401:
|
||||
return T_HTTP_CODE_401;
|
||||
case 402:
|
||||
return T_HTTP_CODE_402;
|
||||
case 403:
|
||||
return T_HTTP_CODE_403;
|
||||
case 404:
|
||||
return T_HTTP_CODE_404;
|
||||
case 405:
|
||||
return T_HTTP_CODE_405;
|
||||
case 406:
|
||||
return T_HTTP_CODE_406;
|
||||
case 407:
|
||||
return T_HTTP_CODE_407;
|
||||
case 408:
|
||||
return T_HTTP_CODE_408;
|
||||
case 409:
|
||||
return T_HTTP_CODE_409;
|
||||
case 410:
|
||||
return T_HTTP_CODE_410;
|
||||
case 411:
|
||||
return T_HTTP_CODE_411;
|
||||
case 412:
|
||||
return T_HTTP_CODE_412;
|
||||
case 413:
|
||||
return T_HTTP_CODE_413;
|
||||
case 414:
|
||||
return T_HTTP_CODE_414;
|
||||
case 415:
|
||||
return T_HTTP_CODE_415;
|
||||
case 416:
|
||||
return T_HTTP_CODE_416;
|
||||
case 417:
|
||||
return T_HTTP_CODE_417;
|
||||
case 429:
|
||||
return T_HTTP_CODE_429;
|
||||
case 500:
|
||||
return T_HTTP_CODE_500;
|
||||
case 501:
|
||||
return T_HTTP_CODE_501;
|
||||
case 502:
|
||||
return T_HTTP_CODE_502;
|
||||
case 503:
|
||||
return T_HTTP_CODE_503;
|
||||
case 504:
|
||||
return T_HTTP_CODE_504;
|
||||
case 505:
|
||||
return T_HTTP_CODE_505;
|
||||
default:
|
||||
return T_HTTP_CODE_ANY;
|
||||
case 100: return T_HTTP_CODE_100;
|
||||
case 101: return T_HTTP_CODE_101;
|
||||
case 200: return T_HTTP_CODE_200;
|
||||
case 201: return T_HTTP_CODE_201;
|
||||
case 202: return T_HTTP_CODE_202;
|
||||
case 203: return T_HTTP_CODE_203;
|
||||
case 204: return T_HTTP_CODE_204;
|
||||
case 205: return T_HTTP_CODE_205;
|
||||
case 206: return T_HTTP_CODE_206;
|
||||
case 300: return T_HTTP_CODE_300;
|
||||
case 301: return T_HTTP_CODE_301;
|
||||
case 302: return T_HTTP_CODE_302;
|
||||
case 303: return T_HTTP_CODE_303;
|
||||
case 304: return T_HTTP_CODE_304;
|
||||
case 305: return T_HTTP_CODE_305;
|
||||
case 307: return T_HTTP_CODE_307;
|
||||
case 400: return T_HTTP_CODE_400;
|
||||
case 401: return T_HTTP_CODE_401;
|
||||
case 402: return T_HTTP_CODE_402;
|
||||
case 403: return T_HTTP_CODE_403;
|
||||
case 404: return T_HTTP_CODE_404;
|
||||
case 405: return T_HTTP_CODE_405;
|
||||
case 406: return T_HTTP_CODE_406;
|
||||
case 407: return T_HTTP_CODE_407;
|
||||
case 408: return T_HTTP_CODE_408;
|
||||
case 409: return T_HTTP_CODE_409;
|
||||
case 410: return T_HTTP_CODE_410;
|
||||
case 411: return T_HTTP_CODE_411;
|
||||
case 412: return T_HTTP_CODE_412;
|
||||
case 413: return T_HTTP_CODE_413;
|
||||
case 414: return T_HTTP_CODE_414;
|
||||
case 415: return T_HTTP_CODE_415;
|
||||
case 416: return T_HTTP_CODE_416;
|
||||
case 417: return T_HTTP_CODE_417;
|
||||
case 429: return T_HTTP_CODE_429;
|
||||
case 500: return T_HTTP_CODE_500;
|
||||
case 501: return T_HTTP_CODE_501;
|
||||
case 502: return T_HTTP_CODE_502;
|
||||
case 503: return T_HTTP_CODE_503;
|
||||
case 504: return T_HTTP_CODE_504;
|
||||
case 505: return T_HTTP_CODE_505;
|
||||
default: return T_HTTP_CODE_ANY;
|
||||
}
|
||||
}
|
||||
|
||||
AsyncWebServerResponse::AsyncWebServerResponse()
|
||||
: _code(0), _contentType(), _contentLength(0), _sendContentLength(true), _chunked(false), _headLength(0), _sentLength(0), _ackedLength(0), _writtenLength(0), _state(RESPONSE_SETUP) {
|
||||
for (const auto& header : DefaultHeaders::Instance()) {
|
||||
: _code(0), _contentType(), _contentLength(0), _sendContentLength(true), _chunked(false), _headLength(0), _sentLength(0), _ackedLength(0), _writtenLength(0),
|
||||
_state(RESPONSE_SETUP) {
|
||||
for (const auto &header : DefaultHeaders::Instance()) {
|
||||
_headers.emplace_back(header);
|
||||
}
|
||||
}
|
||||
|
||||
void AsyncWebServerResponse::setCode(int code) {
|
||||
if (_state == RESPONSE_SETUP)
|
||||
if (_state == RESPONSE_SETUP) {
|
||||
_code = code;
|
||||
}
|
||||
}
|
||||
|
||||
void AsyncWebServerResponse::setContentLength(size_t len) {
|
||||
if (_state == RESPONSE_SETUP && addHeader(T_Content_Length, len, true))
|
||||
if (_state == RESPONSE_SETUP && addHeader(T_Content_Length, len, true)) {
|
||||
_contentLength = len;
|
||||
}
|
||||
}
|
||||
|
||||
void AsyncWebServerResponse::setContentType(const char* type) {
|
||||
if (_state == RESPONSE_SETUP && addHeader(T_Content_Type, type, true))
|
||||
void AsyncWebServerResponse::setContentType(const char *type) {
|
||||
if (_state == RESPONSE_SETUP && addHeader(T_Content_Type, type, true)) {
|
||||
_contentType = type;
|
||||
}
|
||||
}
|
||||
|
||||
bool AsyncWebServerResponse::removeHeader(const char* name) {
|
||||
for (auto i = _headers.begin(); i != _headers.end(); ++i) {
|
||||
bool AsyncWebServerResponse::removeHeader(const char *name) {
|
||||
bool h_erased = false;
|
||||
for (auto i = _headers.begin(); i != _headers.end();) {
|
||||
if (i->name().equalsIgnoreCase(name)) {
|
||||
_headers.erase(i);
|
||||
h_erased = true;
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
return h_erased;
|
||||
}
|
||||
|
||||
bool AsyncWebServerResponse::removeHeader(const char *name, const char *value) {
|
||||
for (auto i = _headers.begin(); i != _headers.end(); ++i) {
|
||||
if (i->name().equalsIgnoreCase(name) && i->value().equalsIgnoreCase(value)) {
|
||||
_headers.erase(i);
|
||||
return true;
|
||||
}
|
||||
|
@ -158,12 +118,23 @@ bool AsyncWebServerResponse::removeHeader(const char* name) {
|
|||
return false;
|
||||
}
|
||||
|
||||
const AsyncWebHeader* AsyncWebServerResponse::getHeader(const char* name) const {
|
||||
auto iter = std::find_if(std::begin(_headers), std::end(_headers), [&name](const AsyncWebHeader& header) { return header.name().equalsIgnoreCase(name); });
|
||||
const AsyncWebHeader *AsyncWebServerResponse::getHeader(const char *name) const {
|
||||
auto iter = std::find_if(std::begin(_headers), std::end(_headers), [&name](const AsyncWebHeader &header) {
|
||||
return header.name().equalsIgnoreCase(name);
|
||||
});
|
||||
return (iter == std::end(_headers)) ? nullptr : &(*iter);
|
||||
}
|
||||
|
||||
bool AsyncWebServerResponse::addHeader(const char* name, const char* value, bool replaceExisting) {
|
||||
bool AsyncWebServerResponse::headerMustBePresentOnce(const String &name) {
|
||||
for (uint8_t i = 0; i < T_only_once_headers_len; i++) {
|
||||
if (name.equalsIgnoreCase(T_only_once_headers[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AsyncWebServerResponse::addHeader(const char *name, const char *value, bool replaceExisting) {
|
||||
for (auto i = _headers.begin(); i != _headers.end(); ++i) {
|
||||
if (i->name().equalsIgnoreCase(name)) {
|
||||
// header already set
|
||||
|
@ -171,9 +142,11 @@ bool AsyncWebServerResponse::addHeader(const char* name, const char* value, bool
|
|||
// remove, break and add the new one
|
||||
_headers.erase(i);
|
||||
break;
|
||||
} else {
|
||||
} else if (headerMustBePresentOnce(i->name())) { // we can have only one header with that name
|
||||
// do not update
|
||||
return false;
|
||||
} else {
|
||||
break; // accept multiple headers with the same name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -182,24 +155,28 @@ bool AsyncWebServerResponse::addHeader(const char* name, const char* value, bool
|
|||
return true;
|
||||
}
|
||||
|
||||
void AsyncWebServerResponse::_assembleHead(String& buffer, uint8_t version) {
|
||||
void AsyncWebServerResponse::_assembleHead(String &buffer, uint8_t version) {
|
||||
if (version) {
|
||||
addHeader(T_Accept_Ranges, T_none, false);
|
||||
if (_chunked)
|
||||
if (_chunked) {
|
||||
addHeader(T_Transfer_Encoding, T_chunked, false);
|
||||
}
|
||||
}
|
||||
|
||||
if (_sendContentLength)
|
||||
if (_sendContentLength) {
|
||||
addHeader(T_Content_Length, String(_contentLength), false);
|
||||
}
|
||||
|
||||
if (_contentType.length())
|
||||
if (_contentType.length()) {
|
||||
addHeader(T_Content_Type, _contentType.c_str(), false);
|
||||
}
|
||||
|
||||
// precompute buffer size to avoid reallocations by String class
|
||||
size_t len = 0;
|
||||
len += 50; // HTTP/1.1 200 <reason>\r\n
|
||||
for (const auto& header : _headers)
|
||||
len += 50; // HTTP/1.1 200 <reason>\r\n
|
||||
for (const auto &header : _headers) {
|
||||
len += header.name().length() + header.value().length() + 4;
|
||||
}
|
||||
|
||||
// prepare buffer
|
||||
buffer.reserve(len);
|
||||
|
@ -218,7 +195,7 @@ void AsyncWebServerResponse::_assembleHead(String& buffer, uint8_t version) {
|
|||
buffer.concat(T_rn);
|
||||
|
||||
// Add headers
|
||||
for (const auto& header : _headers) {
|
||||
for (const auto &header : _headers) {
|
||||
buffer.concat(header.name());
|
||||
#ifdef ESP8266
|
||||
buffer.concat(PSTR(": "));
|
||||
|
@ -233,15 +210,23 @@ void AsyncWebServerResponse::_assembleHead(String& buffer, uint8_t version) {
|
|||
_headLength = buffer.length();
|
||||
}
|
||||
|
||||
bool AsyncWebServerResponse::_started() const { return _state > RESPONSE_SETUP; }
|
||||
bool AsyncWebServerResponse::_finished() const { return _state > RESPONSE_WAIT_ACK; }
|
||||
bool AsyncWebServerResponse::_failed() const { return _state == RESPONSE_FAILED; }
|
||||
bool AsyncWebServerResponse::_sourceValid() const { return false; }
|
||||
void AsyncWebServerResponse::_respond(AsyncWebServerRequest* request) {
|
||||
bool AsyncWebServerResponse::_started() const {
|
||||
return _state > RESPONSE_SETUP;
|
||||
}
|
||||
bool AsyncWebServerResponse::_finished() const {
|
||||
return _state > RESPONSE_WAIT_ACK;
|
||||
}
|
||||
bool AsyncWebServerResponse::_failed() const {
|
||||
return _state == RESPONSE_FAILED;
|
||||
}
|
||||
bool AsyncWebServerResponse::_sourceValid() const {
|
||||
return false;
|
||||
}
|
||||
void AsyncWebServerResponse::_respond(AsyncWebServerRequest *request) {
|
||||
_state = RESPONSE_END;
|
||||
request->client()->close();
|
||||
}
|
||||
size_t AsyncWebServerResponse::_ack(AsyncWebServerRequest* request, size_t len, uint32_t time) {
|
||||
size_t AsyncWebServerResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time) {
|
||||
(void)request;
|
||||
(void)len;
|
||||
(void)time;
|
||||
|
@ -251,19 +236,20 @@ size_t AsyncWebServerResponse::_ack(AsyncWebServerRequest* request, size_t len,
|
|||
/*
|
||||
* String/Code Response
|
||||
* */
|
||||
AsyncBasicResponse::AsyncBasicResponse(int code, const char* contentType, const char* content) {
|
||||
AsyncBasicResponse::AsyncBasicResponse(int code, const char *contentType, const char *content) {
|
||||
_code = code;
|
||||
_content = content;
|
||||
_contentType = contentType;
|
||||
if (_content.length()) {
|
||||
_contentLength = _content.length();
|
||||
if (!_contentType.length())
|
||||
if (!_contentType.length()) {
|
||||
_contentType = T_text_plain;
|
||||
}
|
||||
}
|
||||
addHeader(T_Connection, T_close, false);
|
||||
}
|
||||
|
||||
void AsyncBasicResponse::_respond(AsyncWebServerRequest* request) {
|
||||
void AsyncBasicResponse::_respond(AsyncWebServerRequest *request) {
|
||||
_state = RESPONSE_HEADERS;
|
||||
String out;
|
||||
_assembleHead(out, request->version());
|
||||
|
@ -298,7 +284,7 @@ void AsyncBasicResponse::_respond(AsyncWebServerRequest* request) {
|
|||
}
|
||||
}
|
||||
|
||||
size_t AsyncBasicResponse::_ack(AsyncWebServerRequest* request, size_t len, uint32_t time) {
|
||||
size_t AsyncBasicResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time) {
|
||||
(void)time;
|
||||
_ackedLength += len;
|
||||
if (_state == RESPONSE_CONTENT) {
|
||||
|
@ -338,14 +324,14 @@ AsyncAbstractResponse::AsyncAbstractResponse(AwsTemplateProcessor callback) : _c
|
|||
}
|
||||
}
|
||||
|
||||
void AsyncAbstractResponse::_respond(AsyncWebServerRequest* request) {
|
||||
void AsyncAbstractResponse::_respond(AsyncWebServerRequest *request) {
|
||||
addHeader(T_Connection, T_close, false);
|
||||
_assembleHead(_head, request->version());
|
||||
_state = RESPONSE_HEADERS;
|
||||
_ack(request, 0, 0);
|
||||
}
|
||||
|
||||
size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest* request, size_t len, uint32_t time) {
|
||||
size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time) {
|
||||
(void)time;
|
||||
if (!_sourceValid()) {
|
||||
_state = RESPONSE_FAILED;
|
||||
|
@ -355,14 +341,15 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest* request, size_t len, u
|
|||
|
||||
#if ASYNCWEBSERVER_USE_CHUNK_INFLIGHT
|
||||
// return a credit for each chunk of acked data (polls does not give any credits)
|
||||
if (len)
|
||||
if (len) {
|
||||
++_in_flight_credit;
|
||||
}
|
||||
|
||||
// for chunked responses ignore acks if there are no _in_flight_credits left
|
||||
if (_chunked && !_in_flight_credit) {
|
||||
#ifdef ESP32
|
||||
#ifdef ESP32
|
||||
log_d("(chunk) out of in-flight credits");
|
||||
#endif
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -384,7 +371,7 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest* request, size_t len, u
|
|||
_writtenLength += request->client()->write(out.c_str(), out.length());
|
||||
#if ASYNCWEBSERVER_USE_CHUNK_INFLIGHT
|
||||
_in_flight += out.length();
|
||||
--_in_flight_credit; // take a credit
|
||||
--_in_flight_credit; // take a credit
|
||||
#endif
|
||||
return out.length();
|
||||
}
|
||||
|
@ -399,8 +386,9 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest* request, size_t len, u
|
|||
if (_in_flight > space) {
|
||||
// log_d("defer user call %u/%u", _in_flight, space);
|
||||
// take the credit back since we are ignoring this ack and rely on other inflight data
|
||||
if (len)
|
||||
if (len) {
|
||||
--_in_flight_credit;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
@ -418,9 +406,12 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest* request, size_t len, u
|
|||
outLen = ((_contentLength - _sentLength) > space) ? space : (_contentLength - _sentLength);
|
||||
}
|
||||
|
||||
uint8_t* buf = (uint8_t*)malloc(outLen + headLen);
|
||||
uint8_t *buf = (uint8_t *)malloc(outLen + headLen);
|
||||
if (!buf) {
|
||||
// os_printf("_ack malloc %d failed\n", outLen+headLen);
|
||||
#ifdef ESP32
|
||||
log_e("Failed to allocate");
|
||||
#endif
|
||||
request->abort();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -438,7 +429,7 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest* request, size_t len, u
|
|||
free(buf);
|
||||
return 0;
|
||||
}
|
||||
outLen = sprintf((char*)buf + headLen, "%04x", readLen) + headLen;
|
||||
outLen = sprintf((char *)buf + headLen, "%04x", readLen) + headLen;
|
||||
buf[outLen++] = '\r';
|
||||
buf[outLen++] = '\n';
|
||||
outLen += readLen;
|
||||
|
@ -458,10 +449,10 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest* request, size_t len, u
|
|||
}
|
||||
|
||||
if (outLen) {
|
||||
_writtenLength += request->client()->write((const char*)buf, outLen);
|
||||
_writtenLength += request->client()->write((const char *)buf, outLen);
|
||||
#if ASYNCWEBSERVER_USE_CHUNK_INFLIGHT
|
||||
_in_flight += outLen;
|
||||
--_in_flight_credit; // take a credit
|
||||
--_in_flight_credit; // take a credit
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -481,14 +472,15 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest* request, size_t len, u
|
|||
} else if (_state == RESPONSE_WAIT_ACK) {
|
||||
if (!_sendContentLength || _ackedLength >= _writtenLength) {
|
||||
_state = RESPONSE_END;
|
||||
if (!_chunked && !_sendContentLength)
|
||||
if (!_chunked && !_sendContentLength) {
|
||||
request->client()->close(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t AsyncAbstractResponse::_readDataFromCacheOrContent(uint8_t* data, const size_t len) {
|
||||
size_t AsyncAbstractResponse::_readDataFromCacheOrContent(uint8_t *data, const size_t len) {
|
||||
// If we have something in cache, copy it to buffer
|
||||
const size_t readFromCache = std::min(len, _cache.size());
|
||||
if (readFromCache) {
|
||||
|
@ -501,17 +493,20 @@ size_t AsyncAbstractResponse::_readDataFromCacheOrContent(uint8_t* data, const s
|
|||
return readFromCache + readFromContent;
|
||||
}
|
||||
|
||||
size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size_t len) {
|
||||
if (!_callback)
|
||||
size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t *data, size_t len) {
|
||||
if (!_callback) {
|
||||
return _fillBuffer(data, len);
|
||||
}
|
||||
|
||||
const size_t originalLen = len;
|
||||
len = _readDataFromCacheOrContent(data, len);
|
||||
// Now we've read 'len' bytes, either from cache or from file
|
||||
// Search for template placeholders
|
||||
uint8_t* pTemplateStart = data;
|
||||
while ((pTemplateStart < &data[len]) && (pTemplateStart = (uint8_t*)memchr(pTemplateStart, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart + 1))) { // data[0] ... data[len - 1]
|
||||
uint8_t* pTemplateEnd = (pTemplateStart < &data[len - 1]) ? (uint8_t*)memchr(pTemplateStart + 1, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart) : nullptr;
|
||||
uint8_t *pTemplateStart = data;
|
||||
while ((pTemplateStart < &data[len]) && (pTemplateStart = (uint8_t *)memchr(pTemplateStart, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart + 1))
|
||||
) { // data[0] ... data[len - 1]
|
||||
uint8_t *pTemplateEnd =
|
||||
(pTemplateStart < &data[len - 1]) ? (uint8_t *)memchr(pTemplateStart + 1, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart) : nullptr;
|
||||
// temporary buffer to hold parameter name
|
||||
uint8_t buf[TEMPLATE_PARAM_NAME_LENGTH + 1];
|
||||
String paramName;
|
||||
|
@ -522,35 +517,39 @@ size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size
|
|||
if (paramNameLength) {
|
||||
memcpy(buf, pTemplateStart + 1, paramNameLength);
|
||||
buf[paramNameLength] = 0;
|
||||
paramName = String(reinterpret_cast<char*>(buf));
|
||||
} else { // double percent sign encountered, this is single percent sign escaped.
|
||||
paramName = String(reinterpret_cast<char *>(buf));
|
||||
} else { // double percent sign encountered, this is single percent sign escaped.
|
||||
// remove the 2nd percent sign
|
||||
memmove(pTemplateEnd, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1);
|
||||
len += _readDataFromCacheOrContent(&data[len - 1], 1) - 1;
|
||||
++pTemplateStart;
|
||||
}
|
||||
} else if (&data[len - 1] - pTemplateStart + 1 < TEMPLATE_PARAM_NAME_LENGTH + 2) { // closing placeholder not found, check if it's in the remaining file data
|
||||
} else if (&data[len - 1] - pTemplateStart + 1
|
||||
< TEMPLATE_PARAM_NAME_LENGTH + 2) { // closing placeholder not found, check if it's in the remaining file data
|
||||
memcpy(buf, pTemplateStart + 1, &data[len - 1] - pTemplateStart);
|
||||
const size_t readFromCacheOrContent = _readDataFromCacheOrContent(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PARAM_NAME_LENGTH + 2 - (&data[len - 1] - pTemplateStart + 1));
|
||||
const size_t readFromCacheOrContent =
|
||||
_readDataFromCacheOrContent(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PARAM_NAME_LENGTH + 2 - (&data[len - 1] - pTemplateStart + 1));
|
||||
if (readFromCacheOrContent) {
|
||||
pTemplateEnd = (uint8_t*)memchr(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PLACEHOLDER, readFromCacheOrContent);
|
||||
pTemplateEnd = (uint8_t *)memchr(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PLACEHOLDER, readFromCacheOrContent);
|
||||
if (pTemplateEnd) {
|
||||
// prepare argument to callback
|
||||
*pTemplateEnd = 0;
|
||||
paramName = String(reinterpret_cast<char*>(buf));
|
||||
paramName = String(reinterpret_cast<char *>(buf));
|
||||
// Copy remaining read-ahead data into cache
|
||||
_cache.insert(_cache.begin(), pTemplateEnd + 1, buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent);
|
||||
pTemplateEnd = &data[len - 1];
|
||||
} else // closing placeholder not found in file data, store found percent symbol as is and advance to the next position
|
||||
} else // closing placeholder not found in file data, store found percent symbol as is and advance to the next position
|
||||
{
|
||||
// but first, store read file data in cache
|
||||
_cache.insert(_cache.begin(), buf + (&data[len - 1] - pTemplateStart), buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent);
|
||||
++pTemplateStart;
|
||||
}
|
||||
} else // closing placeholder not found in content data, store found percent symbol as is and advance to the next position
|
||||
} else { // closing placeholder not found in content data, store found percent symbol as is and advance to the next position
|
||||
++pTemplateStart;
|
||||
} else // closing placeholder not found in content data, store found percent symbol as is and advance to the next position
|
||||
}
|
||||
} else { // closing placeholder not found in content data, store found percent symbol as is and advance to the next position
|
||||
++pTemplateStart;
|
||||
}
|
||||
if (paramName.length()) {
|
||||
// call callback and replace with result.
|
||||
// Everything in range [pTemplateStart, pTemplateEnd] can be safely replaced with parameter value.
|
||||
|
@ -558,7 +557,7 @@ size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size
|
|||
// The first byte of data after placeholder is located at pTemplateEnd + 1.
|
||||
// It should be located at pTemplateStart + numBytesCopied (to begin right after inserted parameter value).
|
||||
const String paramValue(_callback(paramName));
|
||||
const char* pvstr = paramValue.c_str();
|
||||
const char *pvstr = paramValue.c_str();
|
||||
const unsigned int pvlen = paramValue.length();
|
||||
const size_t numBytesCopied = std::min(pvlen, static_cast<unsigned int>(&data[originalLen - 1] - pTemplateStart + 1));
|
||||
// make room for param value
|
||||
|
@ -567,27 +566,28 @@ size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size
|
|||
_cache.insert(_cache.begin(), &data[originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1)], &data[len]);
|
||||
// 2. parameter value is longer than placeholder text, push the data after placeholder which not saved into cache further to the end
|
||||
memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[originalLen] - pTemplateStart - numBytesCopied);
|
||||
len = originalLen; // fix issue with truncated data, not sure if it has any side effects
|
||||
} else if (pTemplateEnd + 1 != pTemplateStart + numBytesCopied)
|
||||
len = originalLen; // fix issue with truncated data, not sure if it has any side effects
|
||||
} else if (pTemplateEnd + 1 != pTemplateStart + numBytesCopied) {
|
||||
// 2. Either parameter value is shorter than placeholder text OR there is enough free space in buffer to fit.
|
||||
// Move the entire data after the placeholder
|
||||
memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1);
|
||||
}
|
||||
// 3. replace placeholder with actual value
|
||||
memcpy(pTemplateStart, pvstr, numBytesCopied);
|
||||
// If result is longer than buffer, copy the remainder into cache (this could happen only if placeholder text itself did not fit entirely in buffer)
|
||||
if (numBytesCopied < pvlen) {
|
||||
_cache.insert(_cache.begin(), pvstr + numBytesCopied, pvstr + pvlen);
|
||||
} else if (pTemplateStart + numBytesCopied < pTemplateEnd + 1) { // result is copied fully; if result is shorter than placeholder text...
|
||||
} else if (pTemplateStart + numBytesCopied < pTemplateEnd + 1) { // result is copied fully; if result is shorter than placeholder text...
|
||||
// there is some free room, fill it from cache
|
||||
const size_t roomFreed = pTemplateEnd + 1 - pTemplateStart - numBytesCopied;
|
||||
const size_t totalFreeRoom = originalLen - len + roomFreed;
|
||||
len += _readDataFromCacheOrContent(&data[len - roomFreed], totalFreeRoom) - roomFreed;
|
||||
} else { // result is copied fully; it is longer than placeholder text
|
||||
} else { // result is copied fully; it is longer than placeholder text
|
||||
const size_t roomTaken = pTemplateStart + numBytesCopied - pTemplateEnd - 1;
|
||||
len = std::min(len + roomTaken, originalLen);
|
||||
}
|
||||
}
|
||||
} // while(pTemplateStart)
|
||||
} // while(pTemplateStart)
|
||||
return len;
|
||||
}
|
||||
|
||||
|
@ -595,64 +595,66 @@ size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size
|
|||
* File Response
|
||||
* */
|
||||
|
||||
void AsyncFileResponse::_setContentTypeFromPath(const String& path) {
|
||||
void AsyncFileResponse::_setContentTypeFromPath(const String &path) {
|
||||
#if HAVE_EXTERN_GET_Content_Type_FUNCTION
|
||||
#ifndef ESP8266
|
||||
extern const char* getContentType(const String& path);
|
||||
#else
|
||||
extern const __FlashStringHelper* getContentType(const String& path);
|
||||
#endif
|
||||
#ifndef ESP8266
|
||||
extern const char *getContentType(const String &path);
|
||||
#else
|
||||
extern const __FlashStringHelper *getContentType(const String &path);
|
||||
#endif
|
||||
_contentType = getContentType(path);
|
||||
#else
|
||||
if (path.endsWith(T__html))
|
||||
if (path.endsWith(T__html)) {
|
||||
_contentType = T_text_html;
|
||||
else if (path.endsWith(T__htm))
|
||||
} else if (path.endsWith(T__htm)) {
|
||||
_contentType = T_text_html;
|
||||
else if (path.endsWith(T__css))
|
||||
} else if (path.endsWith(T__css)) {
|
||||
_contentType = T_text_css;
|
||||
else if (path.endsWith(T__json))
|
||||
} else if (path.endsWith(T__json)) {
|
||||
_contentType = T_application_json;
|
||||
else if (path.endsWith(T__js))
|
||||
} else if (path.endsWith(T__js)) {
|
||||
_contentType = T_application_javascript;
|
||||
else if (path.endsWith(T__png))
|
||||
} else if (path.endsWith(T__png)) {
|
||||
_contentType = T_image_png;
|
||||
else if (path.endsWith(T__gif))
|
||||
} else if (path.endsWith(T__gif)) {
|
||||
_contentType = T_image_gif;
|
||||
else if (path.endsWith(T__jpg))
|
||||
} else if (path.endsWith(T__jpg)) {
|
||||
_contentType = T_image_jpeg;
|
||||
else if (path.endsWith(T__ico))
|
||||
} else if (path.endsWith(T__ico)) {
|
||||
_contentType = T_image_x_icon;
|
||||
else if (path.endsWith(T__svg))
|
||||
} else if (path.endsWith(T__svg)) {
|
||||
_contentType = T_image_svg_xml;
|
||||
else if (path.endsWith(T__eot))
|
||||
} else if (path.endsWith(T__eot)) {
|
||||
_contentType = T_font_eot;
|
||||
else if (path.endsWith(T__woff))
|
||||
} else if (path.endsWith(T__woff)) {
|
||||
_contentType = T_font_woff;
|
||||
else if (path.endsWith(T__woff2))
|
||||
} else if (path.endsWith(T__woff2)) {
|
||||
_contentType = T_font_woff2;
|
||||
else if (path.endsWith(T__ttf))
|
||||
} else if (path.endsWith(T__ttf)) {
|
||||
_contentType = T_font_ttf;
|
||||
else if (path.endsWith(T__xml))
|
||||
} else if (path.endsWith(T__xml)) {
|
||||
_contentType = T_text_xml;
|
||||
else if (path.endsWith(T__pdf))
|
||||
} else if (path.endsWith(T__pdf)) {
|
||||
_contentType = T_application_pdf;
|
||||
else if (path.endsWith(T__zip))
|
||||
} else if (path.endsWith(T__zip)) {
|
||||
_contentType = T_application_zip;
|
||||
else if (path.endsWith(T__gz))
|
||||
} else if (path.endsWith(T__gz)) {
|
||||
_contentType = T_application_x_gzip;
|
||||
else
|
||||
} else {
|
||||
_contentType = T_text_plain;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
AsyncFileResponse::AsyncFileResponse(FS& fs, const String& path, const char* contentType, bool download, AwsTemplateProcessor callback) : AsyncAbstractResponse(callback) {
|
||||
AsyncFileResponse::AsyncFileResponse(FS &fs, const String &path, const char *contentType, bool download, AwsTemplateProcessor callback)
|
||||
: AsyncAbstractResponse(callback) {
|
||||
_code = 200;
|
||||
_path = path;
|
||||
|
||||
if (!download && !fs.exists(_path) && fs.exists(_path + T__gz)) {
|
||||
_path = _path + T__gz;
|
||||
addHeader(T_Content_Encoding, T_gzip, false);
|
||||
_callback = nullptr; // Unable to process zipped templates
|
||||
_callback = nullptr; // Unable to process zipped templates
|
||||
_sendContentLength = true;
|
||||
_chunked = false;
|
||||
}
|
||||
|
@ -660,14 +662,15 @@ AsyncFileResponse::AsyncFileResponse(FS& fs, const String& path, const char* con
|
|||
_content = fs.open(_path, fs::FileOpenMode::read);
|
||||
_contentLength = _content.size();
|
||||
|
||||
if (strlen(contentType) == 0)
|
||||
if (strlen(contentType) == 0) {
|
||||
_setContentTypeFromPath(path);
|
||||
else
|
||||
} else {
|
||||
_contentType = contentType;
|
||||
}
|
||||
|
||||
int filenameStart = path.lastIndexOf('/') + 1;
|
||||
char buf[26 + path.length() - filenameStart];
|
||||
char* filename = (char*)path.c_str() + filenameStart;
|
||||
char *filename = (char *)path.c_str() + filenameStart;
|
||||
|
||||
if (download) {
|
||||
// set filename and force download
|
||||
|
@ -679,13 +682,14 @@ AsyncFileResponse::AsyncFileResponse(FS& fs, const String& path, const char* con
|
|||
addHeader(T_Content_Disposition, buf, false);
|
||||
}
|
||||
|
||||
AsyncFileResponse::AsyncFileResponse(File content, const String& path, const char* contentType, bool download, AwsTemplateProcessor callback) : AsyncAbstractResponse(callback) {
|
||||
AsyncFileResponse::AsyncFileResponse(File content, const String &path, const char *contentType, bool download, AwsTemplateProcessor callback)
|
||||
: AsyncAbstractResponse(callback) {
|
||||
_code = 200;
|
||||
_path = path;
|
||||
|
||||
if (!download && String(content.name()).endsWith(T__gz) && !path.endsWith(T__gz)) {
|
||||
addHeader(T_Content_Encoding, T_gzip, false);
|
||||
_callback = nullptr; // Unable to process gzipped templates
|
||||
_callback = nullptr; // Unable to process gzipped templates
|
||||
_sendContentLength = true;
|
||||
_chunked = false;
|
||||
}
|
||||
|
@ -693,14 +697,15 @@ AsyncFileResponse::AsyncFileResponse(File content, const String& path, const cha
|
|||
_content = content;
|
||||
_contentLength = _content.size();
|
||||
|
||||
if (strlen(contentType) == 0)
|
||||
if (strlen(contentType) == 0) {
|
||||
_setContentTypeFromPath(path);
|
||||
else
|
||||
} else {
|
||||
_contentType = contentType;
|
||||
}
|
||||
|
||||
int filenameStart = path.lastIndexOf('/') + 1;
|
||||
char buf[26 + path.length() - filenameStart];
|
||||
char* filename = (char*)path.c_str() + filenameStart;
|
||||
char *filename = (char *)path.c_str() + filenameStart;
|
||||
|
||||
if (download) {
|
||||
snprintf_P(buf, sizeof(buf), PSTR("attachment; filename=\"%s\""), filename);
|
||||
|
@ -710,7 +715,7 @@ AsyncFileResponse::AsyncFileResponse(File content, const String& path, const cha
|
|||
addHeader(T_Content_Disposition, buf, false);
|
||||
}
|
||||
|
||||
size_t AsyncFileResponse::_fillBuffer(uint8_t* data, size_t len) {
|
||||
size_t AsyncFileResponse::_fillBuffer(uint8_t *data, size_t len) {
|
||||
return _content.read(data, len);
|
||||
}
|
||||
|
||||
|
@ -718,19 +723,20 @@ size_t AsyncFileResponse::_fillBuffer(uint8_t* data, size_t len) {
|
|||
* Stream Response
|
||||
* */
|
||||
|
||||
AsyncStreamResponse::AsyncStreamResponse(Stream& stream, const char* contentType, size_t len, AwsTemplateProcessor callback) : AsyncAbstractResponse(callback) {
|
||||
AsyncStreamResponse::AsyncStreamResponse(Stream &stream, const char *contentType, size_t len, AwsTemplateProcessor callback) : AsyncAbstractResponse(callback) {
|
||||
_code = 200;
|
||||
_content = &stream;
|
||||
_contentLength = len;
|
||||
_contentType = contentType;
|
||||
}
|
||||
|
||||
size_t AsyncStreamResponse::_fillBuffer(uint8_t* data, size_t len) {
|
||||
size_t AsyncStreamResponse::_fillBuffer(uint8_t *data, size_t len) {
|
||||
size_t available = _content->available();
|
||||
size_t outLen = (available > len) ? len : available;
|
||||
size_t i;
|
||||
for (i = 0; i < outLen; i++)
|
||||
for (i = 0; i < outLen; i++) {
|
||||
data[i] = _content->read();
|
||||
}
|
||||
return outLen;
|
||||
}
|
||||
|
||||
|
@ -738,17 +744,19 @@ size_t AsyncStreamResponse::_fillBuffer(uint8_t* data, size_t len) {
|
|||
* Callback Response
|
||||
* */
|
||||
|
||||
AsyncCallbackResponse::AsyncCallbackResponse(const char* contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback) : AsyncAbstractResponse(templateCallback) {
|
||||
AsyncCallbackResponse::AsyncCallbackResponse(const char *contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback)
|
||||
: AsyncAbstractResponse(templateCallback) {
|
||||
_code = 200;
|
||||
_content = callback;
|
||||
_contentLength = len;
|
||||
if (!len)
|
||||
if (!len) {
|
||||
_sendContentLength = false;
|
||||
}
|
||||
_contentType = contentType;
|
||||
_filledLength = 0;
|
||||
}
|
||||
|
||||
size_t AsyncCallbackResponse::_fillBuffer(uint8_t* data, size_t len) {
|
||||
size_t AsyncCallbackResponse::_fillBuffer(uint8_t *data, size_t len) {
|
||||
size_t ret = _content(data, len, _filledLength);
|
||||
if (ret != RESPONSE_TRY_AGAIN) {
|
||||
_filledLength += ret;
|
||||
|
@ -760,7 +768,8 @@ size_t AsyncCallbackResponse::_fillBuffer(uint8_t* data, size_t len) {
|
|||
* Chunked Response
|
||||
* */
|
||||
|
||||
AsyncChunkedResponse::AsyncChunkedResponse(const char* contentType, AwsResponseFiller callback, AwsTemplateProcessor processorCallback) : AsyncAbstractResponse(processorCallback) {
|
||||
AsyncChunkedResponse::AsyncChunkedResponse(const char *contentType, AwsResponseFiller callback, AwsTemplateProcessor processorCallback)
|
||||
: AsyncAbstractResponse(processorCallback) {
|
||||
_code = 200;
|
||||
_content = callback;
|
||||
_contentLength = 0;
|
||||
|
@ -770,7 +779,7 @@ AsyncChunkedResponse::AsyncChunkedResponse(const char* contentType, AwsResponseF
|
|||
_filledLength = 0;
|
||||
}
|
||||
|
||||
size_t AsyncChunkedResponse::_fillBuffer(uint8_t* data, size_t len) {
|
||||
size_t AsyncChunkedResponse::_fillBuffer(uint8_t *data, size_t len) {
|
||||
size_t ret = _content(data, len, _filledLength);
|
||||
if (ret != RESPONSE_TRY_AGAIN) {
|
||||
_filledLength += ret;
|
||||
|
@ -782,7 +791,8 @@ size_t AsyncChunkedResponse::_fillBuffer(uint8_t* data, size_t len) {
|
|||
* Progmem Response
|
||||
* */
|
||||
|
||||
AsyncProgmemResponse::AsyncProgmemResponse(int code, const char* contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback) : AsyncAbstractResponse(callback) {
|
||||
AsyncProgmemResponse::AsyncProgmemResponse(int code, const char *contentType, const uint8_t *content, size_t len, AwsTemplateProcessor callback)
|
||||
: AsyncAbstractResponse(callback) {
|
||||
_code = code;
|
||||
_content = content;
|
||||
_contentType = contentType;
|
||||
|
@ -790,7 +800,7 @@ AsyncProgmemResponse::AsyncProgmemResponse(int code, const char* contentType, co
|
|||
_readLength = 0;
|
||||
}
|
||||
|
||||
size_t AsyncProgmemResponse::_fillBuffer(uint8_t* data, size_t len) {
|
||||
size_t AsyncProgmemResponse::_fillBuffer(uint8_t *data, size_t len) {
|
||||
size_t left = _contentLength - _readLength;
|
||||
if (left > len) {
|
||||
memcpy_P(data, _content + _readLength, len);
|
||||
|
@ -806,20 +816,25 @@ size_t AsyncProgmemResponse::_fillBuffer(uint8_t* data, size_t len) {
|
|||
* Response Stream (You can print/write/printf to it, up to the contentLen bytes)
|
||||
* */
|
||||
|
||||
AsyncResponseStream::AsyncResponseStream(const char* contentType, size_t bufferSize) {
|
||||
AsyncResponseStream::AsyncResponseStream(const char *contentType, size_t bufferSize) {
|
||||
_code = 200;
|
||||
_contentLength = 0;
|
||||
_contentType = contentType;
|
||||
_content.reserve(bufferSize);
|
||||
if (!_content.reserve(bufferSize)) {
|
||||
#ifdef ESP32
|
||||
log_e("Failed to allocate");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
size_t AsyncResponseStream::_fillBuffer(uint8_t* buf, size_t maxLen) {
|
||||
return _content.readBytes((char*)buf, maxLen);
|
||||
size_t AsyncResponseStream::_fillBuffer(uint8_t *buf, size_t maxLen) {
|
||||
return _content.readBytes((char *)buf, maxLen);
|
||||
}
|
||||
|
||||
size_t AsyncResponseStream::write(const uint8_t* data, size_t len) {
|
||||
if (_started())
|
||||
size_t AsyncResponseStream::write(const uint8_t *data, size_t len) {
|
||||
if (_started()) {
|
||||
return 0;
|
||||
}
|
||||
size_t written = _content.write(data, len);
|
||||
_contentLength += written;
|
||||
return written;
|
||||
|
|
|
@ -1,29 +1,12 @@
|
|||
/*
|
||||
Asynchronous WebServer library for Espressif MCUs
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
|
||||
|
||||
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
|
||||
This file is part of the esp8266 core for Arduino environment.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
#include "ESPAsyncWebServer.h"
|
||||
#include "WebHandlerImpl.h"
|
||||
|
||||
using namespace asyncsrv;
|
||||
|
||||
bool ON_STA_FILTER(AsyncWebServerRequest* request) {
|
||||
bool ON_STA_FILTER(AsyncWebServerRequest *request) {
|
||||
#ifndef CONFIG_IDF_TARGET_ESP32H2
|
||||
return WiFi.localIP() == request->client()->localIP();
|
||||
#else
|
||||
|
@ -31,7 +14,7 @@ bool ON_STA_FILTER(AsyncWebServerRequest* request) {
|
|||
#endif
|
||||
}
|
||||
|
||||
bool ON_AP_FILTER(AsyncWebServerRequest* request) {
|
||||
bool ON_AP_FILTER(AsyncWebServerRequest *request) {
|
||||
#ifndef CONFIG_IDF_TARGET_ESP32H2
|
||||
return WiFi.localIP() != request->client()->localIP();
|
||||
#else
|
||||
|
@ -40,51 +23,51 @@ bool ON_AP_FILTER(AsyncWebServerRequest* request) {
|
|||
}
|
||||
|
||||
#ifndef HAVE_FS_FILE_OPEN_MODE
|
||||
const char* fs::FileOpenMode::read = "r";
|
||||
const char* fs::FileOpenMode::write = "w";
|
||||
const char* fs::FileOpenMode::append = "a";
|
||||
const char *fs::FileOpenMode::read = "r";
|
||||
const char *fs::FileOpenMode::write = "w";
|
||||
const char *fs::FileOpenMode::append = "a";
|
||||
#endif
|
||||
|
||||
AsyncWebServer::AsyncWebServer(uint16_t port)
|
||||
: _server(port) {
|
||||
AsyncWebServer::AsyncWebServer(uint16_t port) : _server(port) {
|
||||
_catchAllHandler = new AsyncCallbackWebHandler();
|
||||
if (_catchAllHandler == NULL)
|
||||
return;
|
||||
_server.onClient([](void* s, AsyncClient* c) {
|
||||
if (c == NULL)
|
||||
return;
|
||||
c->setRxTimeout(3);
|
||||
AsyncWebServerRequest* r = new AsyncWebServerRequest((AsyncWebServer*)s, c);
|
||||
if (r == NULL) {
|
||||
c->abort();
|
||||
delete c;
|
||||
}
|
||||
},
|
||||
this);
|
||||
_server.onClient(
|
||||
[](void *s, AsyncClient *c) {
|
||||
if (c == NULL) {
|
||||
return;
|
||||
}
|
||||
c->setRxTimeout(3);
|
||||
AsyncWebServerRequest *r = new AsyncWebServerRequest((AsyncWebServer *)s, c);
|
||||
if (r == NULL) {
|
||||
c->abort();
|
||||
delete c;
|
||||
}
|
||||
},
|
||||
this
|
||||
);
|
||||
}
|
||||
|
||||
AsyncWebServer::~AsyncWebServer() {
|
||||
reset();
|
||||
end();
|
||||
if (_catchAllHandler)
|
||||
delete _catchAllHandler;
|
||||
delete _catchAllHandler;
|
||||
_catchAllHandler = nullptr; // Prevent potential use-after-free
|
||||
}
|
||||
|
||||
AsyncWebRewrite& AsyncWebServer::addRewrite(std::shared_ptr<AsyncWebRewrite> rewrite) {
|
||||
AsyncWebRewrite &AsyncWebServer::addRewrite(std::shared_ptr<AsyncWebRewrite> rewrite) {
|
||||
_rewrites.emplace_back(rewrite);
|
||||
return *_rewrites.back().get();
|
||||
}
|
||||
|
||||
AsyncWebRewrite& AsyncWebServer::addRewrite(AsyncWebRewrite* rewrite) {
|
||||
AsyncWebRewrite &AsyncWebServer::addRewrite(AsyncWebRewrite *rewrite) {
|
||||
_rewrites.emplace_back(rewrite);
|
||||
return *_rewrites.back().get();
|
||||
}
|
||||
|
||||
bool AsyncWebServer::removeRewrite(AsyncWebRewrite* rewrite) {
|
||||
bool AsyncWebServer::removeRewrite(AsyncWebRewrite *rewrite) {
|
||||
return removeRewrite(rewrite->from().c_str(), rewrite->toUrl().c_str());
|
||||
}
|
||||
|
||||
bool AsyncWebServer::removeRewrite(const char* from, const char* to) {
|
||||
bool AsyncWebServer::removeRewrite(const char *from, const char *to) {
|
||||
for (auto r = _rewrites.begin(); r != _rewrites.end(); ++r) {
|
||||
if (r->get()->from() == from && r->get()->toUrl() == to) {
|
||||
_rewrites.erase(r);
|
||||
|
@ -94,17 +77,17 @@ bool AsyncWebServer::removeRewrite(const char* from, const char* to) {
|
|||
return false;
|
||||
}
|
||||
|
||||
AsyncWebRewrite& AsyncWebServer::rewrite(const char* from, const char* to) {
|
||||
AsyncWebRewrite &AsyncWebServer::rewrite(const char *from, const char *to) {
|
||||
_rewrites.emplace_back(std::make_shared<AsyncWebRewrite>(from, to));
|
||||
return *_rewrites.back().get();
|
||||
}
|
||||
|
||||
AsyncWebHandler& AsyncWebServer::addHandler(AsyncWebHandler* handler) {
|
||||
AsyncWebHandler &AsyncWebServer::addHandler(AsyncWebHandler *handler) {
|
||||
_handlers.emplace_back(handler);
|
||||
return *(_handlers.back().get());
|
||||
}
|
||||
|
||||
bool AsyncWebServer::removeHandler(AsyncWebHandler* handler) {
|
||||
bool AsyncWebServer::removeHandler(AsyncWebHandler *handler) {
|
||||
for (auto i = _handlers.begin(); i != _handlers.end(); ++i) {
|
||||
if (i->get() == handler) {
|
||||
_handlers.erase(i);
|
||||
|
@ -124,21 +107,23 @@ void AsyncWebServer::end() {
|
|||
}
|
||||
|
||||
#if ASYNC_TCP_SSL_ENABLED
|
||||
void AsyncWebServer::onSslFileRequest(AcSSlFileHandler cb, void* arg) {
|
||||
void AsyncWebServer::onSslFileRequest(AcSSlFileHandler cb, void *arg) {
|
||||
_server.onSslFileRequest(cb, arg);
|
||||
}
|
||||
|
||||
void AsyncWebServer::beginSecure(const char* cert, const char* key, const char* password) {
|
||||
void AsyncWebServer::beginSecure(const char *cert, const char *key, const char *password) {
|
||||
_server.beginSecure(cert, key, password);
|
||||
}
|
||||
#endif
|
||||
|
||||
void AsyncWebServer::_handleDisconnect(AsyncWebServerRequest* request) {
|
||||
void AsyncWebServer::_handleDisconnect(AsyncWebServerRequest *request) {
|
||||
delete request;
|
||||
}
|
||||
|
||||
void AsyncWebServer::_rewriteRequest(AsyncWebServerRequest* request) {
|
||||
for (const auto& r : _rewrites) {
|
||||
void AsyncWebServer::_rewriteRequest(AsyncWebServerRequest *request) {
|
||||
// the last rewrite that matches the request will be used
|
||||
// we do not break the loop to allow for multiple rewrites to be applied and only the last one to be used (allows overriding)
|
||||
for (const auto &r : _rewrites) {
|
||||
if (r->match(request)) {
|
||||
request->_url = r->toUrl();
|
||||
request->_addGetParams(r->params());
|
||||
|
@ -146,8 +131,8 @@ void AsyncWebServer::_rewriteRequest(AsyncWebServerRequest* request) {
|
|||
}
|
||||
}
|
||||
|
||||
void AsyncWebServer::_attachHandler(AsyncWebServerRequest* request) {
|
||||
for (auto& h : _handlers) {
|
||||
void AsyncWebServer::_attachHandler(AsyncWebServerRequest *request) {
|
||||
for (auto &h : _handlers) {
|
||||
if (h->filter(request) && h->canHandle(request)) {
|
||||
request->setHandler(h.get());
|
||||
return;
|
||||
|
@ -157,8 +142,10 @@ void AsyncWebServer::_attachHandler(AsyncWebServerRequest* request) {
|
|||
request->setHandler(_catchAllHandler);
|
||||
}
|
||||
|
||||
AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody) {
|
||||
AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler();
|
||||
AsyncCallbackWebHandler &AsyncWebServer::on(
|
||||
const char *uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody
|
||||
) {
|
||||
AsyncCallbackWebHandler *handler = new AsyncCallbackWebHandler();
|
||||
handler->setUri(uri);
|
||||
handler->setMethod(method);
|
||||
handler->onRequest(onRequest);
|
||||
|
@ -168,8 +155,8 @@ AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodCom
|
|||
return *handler;
|
||||
}
|
||||
|
||||
AsyncStaticWebHandler& AsyncWebServer::serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_control) {
|
||||
AsyncStaticWebHandler* handler = new AsyncStaticWebHandler(uri, fs, path, cache_control);
|
||||
AsyncStaticWebHandler &AsyncWebServer::serveStatic(const char *uri, fs::FS &fs, const char *path, const char *cache_control) {
|
||||
AsyncStaticWebHandler *handler = new AsyncStaticWebHandler(uri, fs, path, cache_control);
|
||||
addHandler(handler);
|
||||
return *handler;
|
||||
}
|
||||
|
@ -186,13 +173,15 @@ void AsyncWebServer::onRequestBody(ArBodyHandlerFunction fn) {
|
|||
_catchAllHandler->onBody(fn);
|
||||
}
|
||||
|
||||
AsyncWebHandler &AsyncWebServer::catchAllHandler() const {
|
||||
return *_catchAllHandler;
|
||||
}
|
||||
|
||||
void AsyncWebServer::reset() {
|
||||
_rewrites.clear();
|
||||
_handlers.clear();
|
||||
|
||||
if (_catchAllHandler != NULL) {
|
||||
_catchAllHandler->onRequest(NULL);
|
||||
_catchAllHandler->onUpload(NULL);
|
||||
_catchAllHandler->onBody(NULL);
|
||||
}
|
||||
_catchAllHandler->onRequest(NULL);
|
||||
_catchAllHandler->onUpload(NULL);
|
||||
_catchAllHandler->onBody(NULL);
|
||||
}
|
||||
|
|
|
@ -1,183 +1,193 @@
|
|||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace asyncsrv {
|
||||
|
||||
static constexpr const char* empty = "";
|
||||
static constexpr const char *empty = "";
|
||||
|
||||
static constexpr const char* T__opaque = "\", opaque=\"";
|
||||
static constexpr const char* T_100_CONTINUE = "100-continue";
|
||||
static constexpr const char* T_13 = "13";
|
||||
static constexpr const char* T_ACCEPT = "accept";
|
||||
static constexpr const char* T_Accept_Ranges = "accept-ranges";
|
||||
static constexpr const char* T_app_xform_urlencoded = "application/x-www-form-urlencoded";
|
||||
static constexpr const char* T_AUTH = "authorization";
|
||||
static constexpr const char* T_auth_nonce = "\", qop=\"auth\", nonce=\"";
|
||||
static constexpr const char* T_BASIC = "basic";
|
||||
static constexpr const char* T_BASIC_REALM = "basic realm=\"";
|
||||
static constexpr const char* T_BEARER = "bearer";
|
||||
static constexpr const char* T_BODY = "body";
|
||||
static constexpr const char* T_Cache_Control = "cache-control";
|
||||
static constexpr const char* T_chunked = "chunked";
|
||||
static constexpr const char* T_close = "close";
|
||||
static constexpr const char* T_cnonce = "cnonce";
|
||||
static constexpr const char* T_Connection = "connection";
|
||||
static constexpr const char* T_Content_Disposition = "content-disposition";
|
||||
static constexpr const char* T_Content_Encoding = "content-encoding";
|
||||
static constexpr const char* T_Content_Length = "content-length";
|
||||
static constexpr const char* T_Content_Type = "content-type";
|
||||
static constexpr const char* T_Cookie = "cookie";
|
||||
static constexpr const char* T_CORS_ACAC = "access-control-allow-credentials";
|
||||
static constexpr const char* T_CORS_ACAH = "access-control-allow-headers";
|
||||
static constexpr const char* T_CORS_ACAM = "access-control-allow-methods";
|
||||
static constexpr const char* T_CORS_ACAO = "access-control-allow-origin";
|
||||
static constexpr const char* T_CORS_ACMA = "access-control-max-age";
|
||||
static constexpr const char* T_CORS_O = "origin";
|
||||
static constexpr const char* T_data_ = "data: ";
|
||||
static constexpr const char* T_DIGEST = "digest";
|
||||
static constexpr const char* T_DIGEST_ = "digest ";
|
||||
static constexpr const char* T_ETag = "etag";
|
||||
static constexpr const char* T_event_ = "event: ";
|
||||
static constexpr const char* T_EXPECT = "expect";
|
||||
static constexpr const char* T_FALSE = "false";
|
||||
static constexpr const char* T_filename = "filename";
|
||||
static constexpr const char* T_gzip = "gzip";
|
||||
static constexpr const char* T_Host = "host";
|
||||
static constexpr const char* T_HTTP_1_0 = "HTTP/1.0";
|
||||
static constexpr const char* T_HTTP_100_CONT = "HTTP/1.1 100 Continue\r\n\r\n";
|
||||
static constexpr const char* T_id__ = "id: ";
|
||||
static constexpr const char* T_IMS = "if-modified-since";
|
||||
static constexpr const char* T_INM = "if-none-match";
|
||||
static constexpr const char* T_keep_alive = "keep-alive";
|
||||
static constexpr const char* T_Last_Event_ID = "last-event-id";
|
||||
static constexpr const char* T_Last_Modified = "last-modified";
|
||||
static constexpr const char* T_LOCATION = "location";
|
||||
static constexpr const char* T_LOGIN_REQ = "Login Required";
|
||||
static constexpr const char* T_MULTIPART_ = "multipart/";
|
||||
static constexpr const char* T_name = "name";
|
||||
static constexpr const char* T_nc = "nc";
|
||||
static constexpr const char* T_no_cache = "no-cache";
|
||||
static constexpr const char* T_nonce = "nonce";
|
||||
static constexpr const char* T_none = "none";
|
||||
static constexpr const char* T_opaque = "opaque";
|
||||
static constexpr const char* T_qop = "qop";
|
||||
static constexpr const char* T_realm = "realm";
|
||||
static constexpr const char* T_realm__ = "realm=\"";
|
||||
static constexpr const char* T_response = "response";
|
||||
static constexpr const char* T_retry_ = "retry: ";
|
||||
static constexpr const char* T_retry_after = "retry-after";
|
||||
static constexpr const char* T_nn = "\n\n";
|
||||
static constexpr const char* T_rn = "\r\n";
|
||||
static constexpr const char* T_rnrn = "\r\n\r\n";
|
||||
static constexpr const char* T_Transfer_Encoding = "transfer-encoding";
|
||||
static constexpr const char* T_TRUE = "true";
|
||||
static constexpr const char* T_UPGRADE = "upgrade";
|
||||
static constexpr const char* T_uri = "uri";
|
||||
static constexpr const char* T_username = "username";
|
||||
static constexpr const char* T_WS = "websocket";
|
||||
static constexpr const char* T_WWW_AUTH = "www-authenticate";
|
||||
static constexpr const char *T__opaque = "\", opaque=\"";
|
||||
static constexpr const char *T_100_CONTINUE = "100-continue";
|
||||
static constexpr const char *T_13 = "13";
|
||||
static constexpr const char *T_ACCEPT = "accept";
|
||||
static constexpr const char *T_Accept_Ranges = "accept-ranges";
|
||||
static constexpr const char *T_app_xform_urlencoded = "application/x-www-form-urlencoded";
|
||||
static constexpr const char *T_AUTH = "authorization";
|
||||
static constexpr const char *T_auth_nonce = "\", qop=\"auth\", nonce=\"";
|
||||
static constexpr const char *T_BASIC = "basic";
|
||||
static constexpr const char *T_BASIC_REALM = "basic realm=\"";
|
||||
static constexpr const char *T_BEARER = "bearer";
|
||||
static constexpr const char *T_BODY = "body";
|
||||
static constexpr const char *T_Cache_Control = "cache-control";
|
||||
static constexpr const char *T_chunked = "chunked";
|
||||
static constexpr const char *T_close = "close";
|
||||
static constexpr const char *T_cnonce = "cnonce";
|
||||
static constexpr const char *T_Connection = "connection";
|
||||
static constexpr const char *T_Content_Disposition = "content-disposition";
|
||||
static constexpr const char *T_Content_Encoding = "content-encoding";
|
||||
static constexpr const char *T_Content_Length = "content-length";
|
||||
static constexpr const char *T_Content_Type = "content-type";
|
||||
static constexpr const char *T_Content_Location = "content-location";
|
||||
static constexpr const char *T_Cookie = "cookie";
|
||||
static constexpr const char *T_CORS_ACAC = "access-control-allow-credentials";
|
||||
static constexpr const char *T_CORS_ACAH = "access-control-allow-headers";
|
||||
static constexpr const char *T_CORS_ACAM = "access-control-allow-methods";
|
||||
static constexpr const char *T_CORS_ACAO = "access-control-allow-origin";
|
||||
static constexpr const char *T_CORS_ACMA = "access-control-max-age";
|
||||
static constexpr const char *T_CORS_O = "origin";
|
||||
static constexpr const char *T_data_ = "data: ";
|
||||
static constexpr const char *T_Date = "date";
|
||||
static constexpr const char *T_DIGEST = "digest";
|
||||
static constexpr const char *T_DIGEST_ = "digest ";
|
||||
static constexpr const char *T_ETag = "etag";
|
||||
static constexpr const char *T_event_ = "event: ";
|
||||
static constexpr const char *T_EXPECT = "expect";
|
||||
static constexpr const char *T_FALSE = "false";
|
||||
static constexpr const char *T_filename = "filename";
|
||||
static constexpr const char *T_gzip = "gzip";
|
||||
static constexpr const char *T_Host = "host";
|
||||
static constexpr const char *T_HTTP_1_0 = "HTTP/1.0";
|
||||
static constexpr const char *T_HTTP_100_CONT = "HTTP/1.1 100 Continue\r\n\r\n";
|
||||
static constexpr const char *T_id__ = "id: ";
|
||||
static constexpr const char *T_IMS = "if-modified-since";
|
||||
static constexpr const char *T_INM = "if-none-match";
|
||||
static constexpr const char *T_keep_alive = "keep-alive";
|
||||
static constexpr const char *T_Last_Event_ID = "last-event-id";
|
||||
static constexpr const char *T_Last_Modified = "last-modified";
|
||||
static constexpr const char *T_LOCATION = "location";
|
||||
static constexpr const char *T_LOGIN_REQ = "Login Required";
|
||||
static constexpr const char *T_MULTIPART_ = "multipart/";
|
||||
static constexpr const char *T_name = "name";
|
||||
static constexpr const char *T_nc = "nc";
|
||||
static constexpr const char *T_no_cache = "no-cache";
|
||||
static constexpr const char *T_nonce = "nonce";
|
||||
static constexpr const char *T_none = "none";
|
||||
static constexpr const char *T_opaque = "opaque";
|
||||
static constexpr const char *T_qop = "qop";
|
||||
static constexpr const char *T_realm = "realm";
|
||||
static constexpr const char *T_realm__ = "realm=\"";
|
||||
static constexpr const char *T_response = "response";
|
||||
static constexpr const char *T_retry_ = "retry: ";
|
||||
static constexpr const char *T_retry_after = "retry-after";
|
||||
static constexpr const char *T_nn = "\n\n";
|
||||
static constexpr const char *T_rn = "\r\n";
|
||||
static constexpr const char *T_rnrn = "\r\n\r\n";
|
||||
static constexpr const char *T_Server = "server";
|
||||
static constexpr const char *T_Transfer_Encoding = "transfer-encoding";
|
||||
static constexpr const char *T_TRUE = "true";
|
||||
static constexpr const char *T_UPGRADE = "upgrade";
|
||||
static constexpr const char *T_uri = "uri";
|
||||
static constexpr const char *T_username = "username";
|
||||
static constexpr const char *T_WS = "websocket";
|
||||
static constexpr const char *T_WWW_AUTH = "www-authenticate";
|
||||
|
||||
// HTTP Methods
|
||||
// HTTP Methods
|
||||
|
||||
static constexpr const char* T_ANY = "ANY";
|
||||
static constexpr const char* T_GET = "GET";
|
||||
static constexpr const char* T_POST = "POST";
|
||||
static constexpr const char* T_PUT = "PUT";
|
||||
static constexpr const char* T_DELETE = "DELETE";
|
||||
static constexpr const char* T_PATCH = "PATCH";
|
||||
static constexpr const char* T_HEAD = "HEAD";
|
||||
static constexpr const char* T_OPTIONS = "OPTIONS";
|
||||
static constexpr const char* T_UNKNOWN = "UNKNOWN";
|
||||
static constexpr const char *T_ANY = "ANY";
|
||||
static constexpr const char *T_GET = "GET";
|
||||
static constexpr const char *T_POST = "POST";
|
||||
static constexpr const char *T_PUT = "PUT";
|
||||
static constexpr const char *T_DELETE = "DELETE";
|
||||
static constexpr const char *T_PATCH = "PATCH";
|
||||
static constexpr const char *T_HEAD = "HEAD";
|
||||
static constexpr const char *T_OPTIONS = "OPTIONS";
|
||||
static constexpr const char *T_UNKNOWN = "UNKNOWN";
|
||||
|
||||
// Req content types
|
||||
static constexpr const char* T_RCT_NOT_USED = "RCT_NOT_USED";
|
||||
static constexpr const char* T_RCT_DEFAULT = "RCT_DEFAULT";
|
||||
static constexpr const char* T_RCT_HTTP = "RCT_HTTP";
|
||||
static constexpr const char* T_RCT_WS = "RCT_WS";
|
||||
static constexpr const char* T_RCT_EVENT = "RCT_EVENT";
|
||||
static constexpr const char* T_ERROR = "ERROR";
|
||||
// Req content types
|
||||
static constexpr const char *T_RCT_NOT_USED = "RCT_NOT_USED";
|
||||
static constexpr const char *T_RCT_DEFAULT = "RCT_DEFAULT";
|
||||
static constexpr const char *T_RCT_HTTP = "RCT_HTTP";
|
||||
static constexpr const char *T_RCT_WS = "RCT_WS";
|
||||
static constexpr const char *T_RCT_EVENT = "RCT_EVENT";
|
||||
static constexpr const char *T_ERROR = "ERROR";
|
||||
|
||||
// extentions & MIME-Types
|
||||
static constexpr const char* T__css = ".css";
|
||||
static constexpr const char* T__eot = ".eot";
|
||||
static constexpr const char* T__gif = ".gif";
|
||||
static constexpr const char* T__gz = ".gz";
|
||||
static constexpr const char* T__htm = ".htm";
|
||||
static constexpr const char* T__html = ".html";
|
||||
static constexpr const char* T__ico = ".ico";
|
||||
static constexpr const char* T__jpg = ".jpg";
|
||||
static constexpr const char* T__js = ".js";
|
||||
static constexpr const char* T__json = ".json";
|
||||
static constexpr const char* T__pdf = ".pdf";
|
||||
static constexpr const char* T__png = ".png";
|
||||
static constexpr const char* T__svg = ".svg";
|
||||
static constexpr const char* T__ttf = ".ttf";
|
||||
static constexpr const char* T__woff = ".woff";
|
||||
static constexpr const char* T__woff2 = ".woff2";
|
||||
static constexpr const char* T__xml = ".xml";
|
||||
static constexpr const char* T__zip = ".zip";
|
||||
static constexpr const char* T_application_javascript = "application/javascript";
|
||||
static constexpr const char* T_application_json = "application/json";
|
||||
static constexpr const char* T_application_msgpack = "application/msgpack";
|
||||
static constexpr const char* T_application_pdf = "application/pdf";
|
||||
static constexpr const char* T_application_x_gzip = "application/x-gzip";
|
||||
static constexpr const char* T_application_zip = "application/zip";
|
||||
static constexpr const char* T_font_eot = "font/eot";
|
||||
static constexpr const char* T_font_ttf = "font/ttf";
|
||||
static constexpr const char* T_font_woff = "font/woff";
|
||||
static constexpr const char* T_font_woff2 = "font/woff2";
|
||||
static constexpr const char* T_image_gif = "image/gif";
|
||||
static constexpr const char* T_image_jpeg = "image/jpeg";
|
||||
static constexpr const char* T_image_png = "image/png";
|
||||
static constexpr const char* T_image_svg_xml = "image/svg+xml";
|
||||
static constexpr const char* T_image_x_icon = "image/x-icon";
|
||||
static constexpr const char* T_text_css = "text/css";
|
||||
static constexpr const char* T_text_event_stream = "text/event-stream";
|
||||
static constexpr const char* T_text_html = "text/html";
|
||||
static constexpr const char* T_text_plain = "text/plain";
|
||||
static constexpr const char* T_text_xml = "text/xml";
|
||||
// extensions & MIME-Types
|
||||
static constexpr const char *T__css = ".css";
|
||||
static constexpr const char *T__eot = ".eot";
|
||||
static constexpr const char *T__gif = ".gif";
|
||||
static constexpr const char *T__gz = ".gz";
|
||||
static constexpr const char *T__htm = ".htm";
|
||||
static constexpr const char *T__html = ".html";
|
||||
static constexpr const char *T__ico = ".ico";
|
||||
static constexpr const char *T__jpg = ".jpg";
|
||||
static constexpr const char *T__js = ".js";
|
||||
static constexpr const char *T__json = ".json";
|
||||
static constexpr const char *T__pdf = ".pdf";
|
||||
static constexpr const char *T__png = ".png";
|
||||
static constexpr const char *T__svg = ".svg";
|
||||
static constexpr const char *T__ttf = ".ttf";
|
||||
static constexpr const char *T__woff = ".woff";
|
||||
static constexpr const char *T__woff2 = ".woff2";
|
||||
static constexpr const char *T__xml = ".xml";
|
||||
static constexpr const char *T__zip = ".zip";
|
||||
static constexpr const char *T_application_javascript = "application/javascript";
|
||||
static constexpr const char *T_application_json = "application/json";
|
||||
static constexpr const char *T_application_msgpack = "application/msgpack";
|
||||
static constexpr const char *T_application_pdf = "application/pdf";
|
||||
static constexpr const char *T_application_x_gzip = "application/x-gzip";
|
||||
static constexpr const char *T_application_zip = "application/zip";
|
||||
static constexpr const char *T_font_eot = "font/eot";
|
||||
static constexpr const char *T_font_ttf = "font/ttf";
|
||||
static constexpr const char *T_font_woff = "font/woff";
|
||||
static constexpr const char *T_font_woff2 = "font/woff2";
|
||||
static constexpr const char *T_image_gif = "image/gif";
|
||||
static constexpr const char *T_image_jpeg = "image/jpeg";
|
||||
static constexpr const char *T_image_png = "image/png";
|
||||
static constexpr const char *T_image_svg_xml = "image/svg+xml";
|
||||
static constexpr const char *T_image_x_icon = "image/x-icon";
|
||||
static constexpr const char *T_text_css = "text/css";
|
||||
static constexpr const char *T_text_event_stream = "text/event-stream";
|
||||
static constexpr const char *T_text_html = "text/html";
|
||||
static constexpr const char *T_text_plain = "text/plain";
|
||||
static constexpr const char *T_text_xml = "text/xml";
|
||||
|
||||
// Responce codes
|
||||
static constexpr const char* T_HTTP_CODE_100 = "Continue";
|
||||
static constexpr const char* T_HTTP_CODE_101 = "Switching Protocols";
|
||||
static constexpr const char* T_HTTP_CODE_200 = "OK";
|
||||
static constexpr const char* T_HTTP_CODE_201 = "Created";
|
||||
static constexpr const char* T_HTTP_CODE_202 = "Accepted";
|
||||
static constexpr const char* T_HTTP_CODE_203 = "Non-Authoritative Information";
|
||||
static constexpr const char* T_HTTP_CODE_204 = "No Content";
|
||||
static constexpr const char* T_HTTP_CODE_205 = "Reset Content";
|
||||
static constexpr const char* T_HTTP_CODE_206 = "Partial Content";
|
||||
static constexpr const char* T_HTTP_CODE_300 = "Multiple Choices";
|
||||
static constexpr const char* T_HTTP_CODE_301 = "Moved Permanently";
|
||||
static constexpr const char* T_HTTP_CODE_302 = "Found";
|
||||
static constexpr const char* T_HTTP_CODE_303 = "See Other";
|
||||
static constexpr const char* T_HTTP_CODE_304 = "Not Modified";
|
||||
static constexpr const char* T_HTTP_CODE_305 = "Use Proxy";
|
||||
static constexpr const char* T_HTTP_CODE_307 = "Temporary Redirect";
|
||||
static constexpr const char* T_HTTP_CODE_400 = "Bad Request";
|
||||
static constexpr const char* T_HTTP_CODE_401 = "Unauthorized";
|
||||
static constexpr const char* T_HTTP_CODE_402 = "Payment Required";
|
||||
static constexpr const char* T_HTTP_CODE_403 = "Forbidden";
|
||||
static constexpr const char* T_HTTP_CODE_404 = "Not Found";
|
||||
static constexpr const char* T_HTTP_CODE_405 = "Method Not Allowed";
|
||||
static constexpr const char* T_HTTP_CODE_406 = "Not Acceptable";
|
||||
static constexpr const char* T_HTTP_CODE_407 = "Proxy Authentication Required";
|
||||
static constexpr const char* T_HTTP_CODE_408 = "Request Time-out";
|
||||
static constexpr const char* T_HTTP_CODE_409 = "Conflict";
|
||||
static constexpr const char* T_HTTP_CODE_410 = "Gone";
|
||||
static constexpr const char* T_HTTP_CODE_411 = "Length Required";
|
||||
static constexpr const char* T_HTTP_CODE_412 = "Precondition Failed";
|
||||
static constexpr const char* T_HTTP_CODE_413 = "Request Entity Too Large";
|
||||
static constexpr const char* T_HTTP_CODE_414 = "Request-URI Too Large";
|
||||
static constexpr const char* T_HTTP_CODE_415 = "Unsupported Media Type";
|
||||
static constexpr const char* T_HTTP_CODE_416 = "Requested range not satisfiable";
|
||||
static constexpr const char* T_HTTP_CODE_417 = "Expectation Failed";
|
||||
static constexpr const char* T_HTTP_CODE_429 = "Too Many Requests";
|
||||
static constexpr const char* T_HTTP_CODE_500 = "Internal Server Error";
|
||||
static constexpr const char* T_HTTP_CODE_501 = "Not Implemented";
|
||||
static constexpr const char* T_HTTP_CODE_502 = "Bad Gateway";
|
||||
static constexpr const char* T_HTTP_CODE_503 = "Service Unavailable";
|
||||
static constexpr const char* T_HTTP_CODE_504 = "Gateway Time-out";
|
||||
static constexpr const char* T_HTTP_CODE_505 = "HTTP Version not supported";
|
||||
static constexpr const char* T_HTTP_CODE_ANY = "Unknown code";
|
||||
// Response codes
|
||||
static constexpr const char *T_HTTP_CODE_100 = "Continue";
|
||||
static constexpr const char *T_HTTP_CODE_101 = "Switching Protocols";
|
||||
static constexpr const char *T_HTTP_CODE_200 = "OK";
|
||||
static constexpr const char *T_HTTP_CODE_201 = "Created";
|
||||
static constexpr const char *T_HTTP_CODE_202 = "Accepted";
|
||||
static constexpr const char *T_HTTP_CODE_203 = "Non-Authoritative Information";
|
||||
static constexpr const char *T_HTTP_CODE_204 = "No Content";
|
||||
static constexpr const char *T_HTTP_CODE_205 = "Reset Content";
|
||||
static constexpr const char *T_HTTP_CODE_206 = "Partial Content";
|
||||
static constexpr const char *T_HTTP_CODE_300 = "Multiple Choices";
|
||||
static constexpr const char *T_HTTP_CODE_301 = "Moved Permanently";
|
||||
static constexpr const char *T_HTTP_CODE_302 = "Found";
|
||||
static constexpr const char *T_HTTP_CODE_303 = "See Other";
|
||||
static constexpr const char *T_HTTP_CODE_304 = "Not Modified";
|
||||
static constexpr const char *T_HTTP_CODE_305 = "Use Proxy";
|
||||
static constexpr const char *T_HTTP_CODE_307 = "Temporary Redirect";
|
||||
static constexpr const char *T_HTTP_CODE_400 = "Bad Request";
|
||||
static constexpr const char *T_HTTP_CODE_401 = "Unauthorized";
|
||||
static constexpr const char *T_HTTP_CODE_402 = "Payment Required";
|
||||
static constexpr const char *T_HTTP_CODE_403 = "Forbidden";
|
||||
static constexpr const char *T_HTTP_CODE_404 = "Not Found";
|
||||
static constexpr const char *T_HTTP_CODE_405 = "Method Not Allowed";
|
||||
static constexpr const char *T_HTTP_CODE_406 = "Not Acceptable";
|
||||
static constexpr const char *T_HTTP_CODE_407 = "Proxy Authentication Required";
|
||||
static constexpr const char *T_HTTP_CODE_408 = "Request Time-out";
|
||||
static constexpr const char *T_HTTP_CODE_409 = "Conflict";
|
||||
static constexpr const char *T_HTTP_CODE_410 = "Gone";
|
||||
static constexpr const char *T_HTTP_CODE_411 = "Length Required";
|
||||
static constexpr const char *T_HTTP_CODE_412 = "Precondition Failed";
|
||||
static constexpr const char *T_HTTP_CODE_413 = "Request Entity Too Large";
|
||||
static constexpr const char *T_HTTP_CODE_414 = "Request-URI Too Large";
|
||||
static constexpr const char *T_HTTP_CODE_415 = "Unsupported Media Type";
|
||||
static constexpr const char *T_HTTP_CODE_416 = "Requested range not satisfiable";
|
||||
static constexpr const char *T_HTTP_CODE_417 = "Expectation Failed";
|
||||
static constexpr const char *T_HTTP_CODE_429 = "Too Many Requests";
|
||||
static constexpr const char *T_HTTP_CODE_500 = "Internal Server Error";
|
||||
static constexpr const char *T_HTTP_CODE_501 = "Not Implemented";
|
||||
static constexpr const char *T_HTTP_CODE_502 = "Bad Gateway";
|
||||
static constexpr const char *T_HTTP_CODE_503 = "Service Unavailable";
|
||||
static constexpr const char *T_HTTP_CODE_504 = "Gateway Time-out";
|
||||
static constexpr const char *T_HTTP_CODE_505 = "HTTP Version not supported";
|
||||
static constexpr const char *T_HTTP_CODE_ANY = "Unknown code";
|
||||
|
||||
} // namespace asyncsrv {}
|
||||
static constexpr const uint8_t T_only_once_headers_len = 11;
|
||||
static constexpr const char *T_only_once_headers[] = {T_Content_Length, T_Content_Type, T_Date, T_ETag, T_Last_Modified, T_LOCATION, T_retry_after,
|
||||
T_Transfer_Encoding, T_Content_Location, T_Server, T_WWW_AUTH};
|
||||
|
||||
} // namespace asyncsrv
|
||||
|
|
|
@ -64,7 +64,7 @@ _____ _ _ ___ _____ _
|
|||
#include "Update.h"
|
||||
#include "StreamString.h"
|
||||
#if ELEGANTOTA_USE_ASYNC_WEBSERVER == 1
|
||||
#include "../../ESP32Async-AsyncTCP/src/AsyncTCP.h"
|
||||
#include "../../mathieucarbou-AsyncTCPSock/src/AsyncTCP.h"
|
||||
#include "../../ESP32Async-ESPAsyncWebServer/src/ESPAsyncWebServer.h"
|
||||
#define ELEGANTOTA_WEBSERVER AsyncWebServer
|
||||
#else
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// ArduinoJson - https://arduinojson.org
|
||||
// Copyright © 2014-2024, Benoit BLANCHON
|
||||
// Copyright © 2014-2025, Benoit BLANCHON
|
||||
// MIT License
|
||||
|
||||
#pragma once
|
||||
|
@ -239,11 +239,11 @@
|
|||
#define ARDUINOJSON_BIN2ALPHA_1111() P
|
||||
#define ARDUINOJSON_BIN2ALPHA_(A, B, C, D) ARDUINOJSON_BIN2ALPHA_##A##B##C##D()
|
||||
#define ARDUINOJSON_BIN2ALPHA(A, B, C, D) ARDUINOJSON_BIN2ALPHA_(A, B, C, D)
|
||||
#define ARDUINOJSON_VERSION "7.3.0"
|
||||
#define ARDUINOJSON_VERSION "7.3.1"
|
||||
#define ARDUINOJSON_VERSION_MAJOR 7
|
||||
#define ARDUINOJSON_VERSION_MINOR 3
|
||||
#define ARDUINOJSON_VERSION_REVISION 0
|
||||
#define ARDUINOJSON_VERSION_MACRO V730
|
||||
#define ARDUINOJSON_VERSION_REVISION 1
|
||||
#define ARDUINOJSON_VERSION_MACRO V731
|
||||
#ifndef ARDUINOJSON_VERSION_NAMESPACE
|
||||
# define ARDUINOJSON_VERSION_NAMESPACE \
|
||||
ARDUINOJSON_CONCAT5( \
|
||||
|
@ -1029,8 +1029,9 @@ template <typename TChar, size_t N>
|
|||
struct StringAdapter<TChar[N], enable_if_t<IsChar<TChar>::value>> {
|
||||
using AdaptedString = RamString;
|
||||
static AdaptedString adapt(const TChar* p) {
|
||||
ARDUINOJSON_ASSERT(p);
|
||||
auto str = reinterpret_cast<const char*>(p);
|
||||
return AdaptedString(str, str ? ::strlen(str) : 0);
|
||||
return AdaptedString(str, ::strlen(str));
|
||||
}
|
||||
};
|
||||
template <typename TChar>
|
||||
|
@ -2243,6 +2244,7 @@ class VariantData {
|
|||
#else
|
||||
(void)resources; // silence warning
|
||||
#endif
|
||||
const char* str = nullptr;
|
||||
switch (type_) {
|
||||
case VariantType::Boolean:
|
||||
return static_cast<T>(content_.asBoolean);
|
||||
|
@ -2257,8 +2259,11 @@ class VariantData {
|
|||
return static_cast<T>(extension->asInt64);
|
||||
#endif
|
||||
case VariantType::LinkedString:
|
||||
str = content_.asLinkedString;
|
||||
break;
|
||||
case VariantType::OwnedString:
|
||||
return parseNumber<T>(content_.asOwnedString->data);
|
||||
str = content_.asOwnedString->data;
|
||||
break;
|
||||
case VariantType::Float:
|
||||
return static_cast<T>(content_.asFloat);
|
||||
#if ARDUINOJSON_USE_DOUBLE
|
||||
|
@ -2266,8 +2271,10 @@ class VariantData {
|
|||
return static_cast<T>(extension->asDouble);
|
||||
#endif
|
||||
default:
|
||||
return 0;
|
||||
return 0.0;
|
||||
}
|
||||
ARDUINOJSON_ASSERT(str != nullptr);
|
||||
return parseNumber<T>(str);
|
||||
}
|
||||
template <typename T>
|
||||
T asIntegral(const ResourceManager* resources) const {
|
||||
|
@ -2277,6 +2284,7 @@ class VariantData {
|
|||
#else
|
||||
(void)resources; // silence warning
|
||||
#endif
|
||||
const char* str = nullptr;
|
||||
switch (type_) {
|
||||
case VariantType::Boolean:
|
||||
return content_.asBoolean;
|
||||
|
@ -2291,9 +2299,11 @@ class VariantData {
|
|||
return convertNumber<T>(extension->asInt64);
|
||||
#endif
|
||||
case VariantType::LinkedString:
|
||||
return parseNumber<T>(content_.asLinkedString);
|
||||
str = content_.asLinkedString;
|
||||
break;
|
||||
case VariantType::OwnedString:
|
||||
return parseNumber<T>(content_.asOwnedString->data);
|
||||
str = content_.asOwnedString->data;
|
||||
break;
|
||||
case VariantType::Float:
|
||||
return convertNumber<T>(content_.asFloat);
|
||||
#if ARDUINOJSON_USE_DOUBLE
|
||||
|
@ -2303,6 +2313,8 @@ class VariantData {
|
|||
default:
|
||||
return 0;
|
||||
}
|
||||
ARDUINOJSON_ASSERT(str != nullptr);
|
||||
return parseNumber<T>(str);
|
||||
}
|
||||
ObjectData* asObject() {
|
||||
return isObject() ? &content_.asObject : 0;
|
||||
|
|
22
Software/src/lib/mathieucarbou-AsyncTCPSock/library.json
Normal file
22
Software/src/lib/mathieucarbou-AsyncTCPSock/library.json
Normal 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
|
||||
}
|
||||
}
|
|
@ -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=*
|
1301
Software/src/lib/mathieucarbou-AsyncTCPSock/src/AsyncTCP.cpp
Normal file
1301
Software/src/lib/mathieucarbou-AsyncTCPSock/src/AsyncTCP.cpp
Normal file
File diff suppressed because it is too large
Load diff
321
Software/src/lib/mathieucarbou-AsyncTCPSock/src/AsyncTCP.h
Normal file
321
Software/src/lib/mathieucarbou-AsyncTCPSock/src/AsyncTCP.h
Normal 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_ */
|
|
@ -0,0 +1,6 @@
|
|||
#ifndef ASYNCTCP_SSL_H_
|
||||
#define ASYNCTCP_SSL_H_
|
||||
|
||||
#include "AsyncTCP_SSL.hpp"
|
||||
|
||||
#endif /* ASYNCTCP_SSL_H_ */
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -264,15 +264,15 @@ int CAN_init() {
|
|||
return 0;
|
||||
}
|
||||
|
||||
int CAN_write_frame(const CAN_frame_t *p_frame) {
|
||||
bool CAN_write_frame(const CAN_frame_t *p_frame) {
|
||||
if (sem_tx_complete == NULL) {
|
||||
return -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write the frame to the controller
|
||||
CAN_write_frame_phy(p_frame);
|
||||
|
||||
return xSemaphoreTake(sem_tx_complete, 20) == pdTRUE ? 0 : -1;
|
||||
return xSemaphoreTake(sem_tx_complete, 20) == pdTRUE ? true : false;
|
||||
}
|
||||
|
||||
int CAN_stop() {
|
||||
|
|
|
@ -93,6 +93,10 @@ typedef struct {
|
|||
uint8_t AMR3; /**< \brief Acceptance Mask Register AMR3 */
|
||||
} CAN_filter_t;
|
||||
|
||||
extern void gpio_matrix_in(uint32_t gpio, uint32_t signal_idx, bool inv);
|
||||
extern void gpio_matrix_out(uint32_t gpio, uint32_t signal_idx, bool out_inv, bool oen_inv);
|
||||
extern void gpio_pad_select_gpio(uint8_t gpio_num);
|
||||
|
||||
/**
|
||||
* \brief Initialize the CAN Module
|
||||
*
|
||||
|
@ -104,9 +108,9 @@ int CAN_init(void);
|
|||
* \brief Send a can frame
|
||||
*
|
||||
* \param p_frame Pointer to the frame to be send, see #CAN_frame_t
|
||||
* \return 0 Frame has been written to the module
|
||||
* \return 1 Frame has been written to the module
|
||||
*/
|
||||
int CAN_write_frame(const CAN_frame_t *p_frame);
|
||||
bool CAN_write_frame(const CAN_frame_t *p_frame);
|
||||
|
||||
/**
|
||||
* \brief Stops the CAN Module
|
||||
|
|
|
@ -5,28 +5,10 @@
|
|||
int ESP32CAN::CANInit() {
|
||||
return CAN_init();
|
||||
}
|
||||
int ESP32CAN::CANWriteFrame(const CAN_frame_t* p_frame) {
|
||||
static unsigned long start_time;
|
||||
int result = -1;
|
||||
if (tx_ok) {
|
||||
result = CAN_write_frame(p_frame);
|
||||
tx_ok = (result == 0) ? true : false;
|
||||
if (tx_ok == false) {
|
||||
#ifdef DEBUG_VIA_USB
|
||||
Serial.println("CAN failure! Check wires");
|
||||
#endif
|
||||
set_event(EVENT_CAN_NATIVE_TX_FAILURE, 0);
|
||||
start_time = millis();
|
||||
} else {
|
||||
clear_event(EVENT_CAN_NATIVE_TX_FAILURE);
|
||||
}
|
||||
} else {
|
||||
if ((millis() - start_time) >= 20) {
|
||||
tx_ok = true;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
bool ESP32CAN::CANWriteFrame(const CAN_frame_t* p_frame) {
|
||||
return CAN_write_frame(p_frame);
|
||||
}
|
||||
|
||||
int ESP32CAN::CANStop() {
|
||||
return CAN_stop();
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ class ESP32CAN {
|
|||
bool tx_ok = true;
|
||||
int CANInit();
|
||||
int CANConfigFilter(const CAN_filter_t* p_filter);
|
||||
int CANWriteFrame(const CAN_frame_t* p_frame);
|
||||
bool CANWriteFrame(const CAN_frame_t* p_frame);
|
||||
int CANStop();
|
||||
void CANSetCfg(CAN_device_t* can_cfg);
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue