Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switch to willmmiles fork of AsyncWebServer (0_15) #3741

Closed
wants to merge 14 commits into from
Closed
2 changes: 1 addition & 1 deletion platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ lib_deps =
fastled/FastLED @ 3.6.0
IRremoteESP8266 @ 2.8.2
makuna/NeoPixelBus @ 2.7.5
https://github.com/Aircoookie/ESPAsyncWebServer.git @ ~2.0.7
https://github.com/willmmiles/ESPAsyncWebServer.git#master
# for I2C interface
;Wire
# ESP-NOW library
Expand Down
33 changes: 33 additions & 0 deletions tools/stress_test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/bin/bash
# Some web server stress tests
#
# Perform a large number of parallel requests, stress testing the web server
# TODO: some kind of performance metrics


TARGET=$1

CURL_ARGS="--compressed --parallel --parallel-immediate --parallel-max 50"

JSON_TARGETS=('json/state' 'json/info' 'json/si', 'json/palettes' 'json/fxdata' 'settings/s.js?p=2')
FILE_TARGETS=('' 'iro.js' 'rangetouch.js' 'settings' 'settings/wifi')

# Replicate one target many times
function replicate() {
printf "${1}?%d " {1..8}
}
read -a JSON_LARGE_TARGETS <<< $(replicate "json/si")
read -a JSON_LARGER_TARGETS <<< $(replicate "json/fxdata")

# TODO: argument parsing

# Test static file targets
#TARGETS=(${JSON_TARGETS[@]})
#TARGETS=(${FILE_TARGETS[@]})
TARGETS=(${JSON_LARGER_TARGETS[@]})

# Expand target URLS to full arguments for curl
FULL_OPTIONS=$(printf "http://${TARGET}/%s -o /dev/null " "${TARGETS[@]}")

#echo ${FULL_OPTIONS}
time curl ${CURL_ARGS} ${FULL_OPTIONS}
13 changes: 11 additions & 2 deletions wled00/const.h
Original file line number Diff line number Diff line change
Expand Up @@ -492,8 +492,17 @@
#endif
#endif

//#define MIN_HEAP_SIZE (8k for AsyncWebServer)
#define MIN_HEAP_SIZE 8192
//#define MIN_HEAP_SIZE
#define MIN_HEAP_SIZE 2048

// Web server limits
#ifdef ESP8266
#define WLED_MAX_CONCURRENT_REQUESTS 2
#define WLED_MAX_QUEUED_REQUESTS 6
#else
#define WLED_MAX_CONCURRENT_REQUESTS 6
#define WLED_MAX_QUEUED_REQUESTS 12
#endif

// Maximum size of node map (list of other WLED instances)
#ifdef ESP8266
Expand Down
2 changes: 1 addition & 1 deletion wled00/fcn_declare.h
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ void sappend(char stype, const char* key, int val);
void sappends(char stype, const char* key, char* val);
void prepareHostname(char* hostname);
bool isAsterisksOnly(const char* str, byte maxLen);
bool requestJSONBufferLock(uint8_t module=255);
bool requestJSONBufferLock(uint8_t module=255, bool yield_ok = true);
void releaseJSONBufferLock();
uint8_t extractModeName(uint8_t mode, const char *src, char *dest, uint8_t maxLen);
uint8_t extractModeSlider(uint8_t mode, uint8_t slider, char *dest, uint8_t maxLen, uint8_t *var = nullptr);
Expand Down
15 changes: 1 addition & 14 deletions wled00/file.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -378,20 +378,7 @@ void updateFSInfo() {
//Un-comment any file types you need
static String getContentType(AsyncWebServerRequest* request, String filename){
if(request->hasArg(F("download"))) return SET_F("application/octet-stream");
else if(filename.endsWith(F(".htm"))) return SET_F("text/html");
else if(filename.endsWith(F(".html"))) return SET_F("text/html");
else if(filename.endsWith(F(".css"))) return SET_F("text/css");
else if(filename.endsWith(F(".js"))) return SET_F("application/javascript");
else if(filename.endsWith(F(".json"))) return SET_F("application/json");
else if(filename.endsWith(F(".png"))) return SET_F("image/png");
else if(filename.endsWith(F(".gif"))) return SET_F("image/gif");
else if(filename.endsWith(F(".jpg"))) return SET_F("image/jpeg");
else if(filename.endsWith(F(".ico"))) return SET_F("image/x-icon");
// else if(filename.endsWith(F(".xml"))) return SET_F("text/xml");
// else if(filename.endsWith(F(".pdf"))) return SET_F("application/x-pdf");
// else if(filename.endsWith(F(".zip"))) return SET_F("application/x-zip");
// else if(filename.endsWith(F(".gz"))) return SET_F("application/x-gzip");
return "text/plain";
return contentTypeFor(filename);
}

#if defined(BOARD_HAS_PSRAM) && defined(WLED_USE_PSRAM)
Expand Down
24 changes: 18 additions & 6 deletions wled00/json.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1023,14 +1023,26 @@ void serializeModeNames(JsonArray arr)

// Global buffer locking response helper class (to make sure lock is released when AsyncJsonResponse is destroyed)
class LockedJsonResponse: public AsyncJsonResponse {
bool _holding_lock;
public:
// WARNING: constructor assumes requestJSONBufferLock() was successfully acquired externally/prior to constructing the instance
// Not a good practice with C++. Unfortunately AsyncJsonResponse only has 2 constructors - for dynamic buffer or existing buffer,
// with existing buffer it clears its content during construction
// if the lock was not acquired (using JSONBufferGuard class) previous implementation still cleared existing buffer
inline LockedJsonResponse(JsonDocument *doc, bool isArray) : AsyncJsonResponse(doc, isArray) {};
inline LockedJsonResponse(JsonDocument* doc, bool isArray) : AsyncJsonResponse(doc, isArray), _holding_lock(true) {};

virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) {
size_t result = AsyncJsonResponse::_fillBuffer(buf, maxLen);
// Release lock as soon as we're done filling content
if (((result + _sentLength) >= (_contentLength)) && _holding_lock) {
releaseJSONBufferLock();
_holding_lock = false;
}
return result;
}

// destructor will remove JSON buffer lock when response is destroyed in AsyncWebServer
virtual ~LockedJsonResponse() { releaseJSONBufferLock(); };
virtual ~LockedJsonResponse() { if (_holding_lock) releaseJSONBufferLock(); };
};

void serveJson(AsyncWebServerRequest* request)
Expand All @@ -1052,7 +1064,7 @@ void serveJson(AsyncWebServerRequest* request)
}
#endif
else if (url.indexOf("pal") > 0) {
request->send_P(200, F("application/json"), JSON_palette_names);
request->send_P(200, FPSTR(CONTENT_TYPE_JSON), JSON_palette_names);
return;
}
else if (url.indexOf("cfg") > 0 && handleFileRead(request, F("/cfg.json"))) {
Expand All @@ -1063,8 +1075,8 @@ void serveJson(AsyncWebServerRequest* request)
return;
}

if (!requestJSONBufferLock(17)) {
serveJsonError(request, 503, ERR_NOBUF);
if (!requestJSONBufferLock(17, false)) {
request->deferResponse();
return;
}
// releaseJSONBufferLock() will be called when "response" is destroyed (from AsyncWebServer)
Expand Down Expand Up @@ -1172,7 +1184,7 @@ bool serveLiveLeds(AsyncWebServerRequest* request, uint32_t wsClient)
#endif
oappend("}");
if (request) {
request->send(200, F("application/json"), buffer);
request->send(200, FPSTR(CONTENT_TYPE_JSON), buffer);
}
#ifdef WLED_ENABLE_WEBSOCKETS
else {
Expand Down
8 changes: 4 additions & 4 deletions wled00/set.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -701,16 +701,16 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
// so checkboxes have one or two fields (first is always "false", existence of second depends on checkmark and may be "true")
if (subObj[name].isNull()) {
// the first occurrence of the field describes the parameter type (used in next loop)
if (value == "false") subObj[name] = false; // checkboxes may have only one field
if (value == F("false")) subObj[name] = false; // checkboxes may have only one field
else subObj[name] = value;
} else {
String type = subObj[name].as<String>(); // get previously stored value as a type
if (subObj[name].is<bool>()) subObj[name] = true; // checkbox/boolean
else if (type == "number") {
else if (type == F("number")) {
value.replace(",","."); // just in case conversion
if (value.indexOf(".") >= 0) subObj[name] = value.toFloat(); // we do have a float
else subObj[name] = value.toInt(); // we may have an int
} else if (type == "int") subObj[name] = value.toInt();
} else if (type == F("int")) subObj[name] = value.toInt();
else subObj[name] = value; // text fields
}
DEBUG_PRINT(F(" = ")); DEBUG_PRINTLN(value);
Expand Down Expand Up @@ -771,7 +771,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
//HTTP API request parser
bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
{
if (!(req.indexOf("win") >= 0)) return false;
if (!(req.indexOf(F("win")) >= 0)) return false;

int pos = 0;
DEBUG_PRINT(F("API req: "));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ AsyncMqttClient::AsyncMqttClient()
sprintf(_generatedClientId, "esp32%06x", (uint32_t)ESP.getEfuseMac());
_xSemaphore = xSemaphoreCreateMutex();
#elif defined(ESP8266)
sprintf(_generatedClientId, "esp8266%06x", (uint32_t)ESP.getChipId());
sprintf_P(_generatedClientId, PSTR("esp8266%06x"), (uint32_t)ESP.getChipId());
#endif
_clientId = _generatedClientId;

Expand Down
6 changes: 2 additions & 4 deletions wled00/src/dependencies/json/AsyncJson-v6.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@
#define DYNAMIC_JSON_DOCUMENT_SIZE 16384
#endif

constexpr const char* JSON_MIMETYPE = "application/json";

/*
* Json Response
* */
Expand Down Expand Up @@ -66,7 +64,7 @@ class AsyncJsonResponse: public AsyncAbstractResponse {

AsyncJsonResponse(JsonDocument *ref, bool isArray=false) : _jsonBuffer(1), _isValid{false} {
_code = 200;
_contentType = JSON_MIMETYPE;
_contentType = FPSTR(CONTENT_TYPE_JSON);
if(isArray)
_root = ref->to<JsonArray>();
else
Expand All @@ -75,7 +73,7 @@ class AsyncJsonResponse: public AsyncAbstractResponse {

AsyncJsonResponse(size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE, bool isArray=false) : _jsonBuffer(maxJsonBufferSize), _isValid{false} {
_code = 200;
_contentType = JSON_MIMETYPE;
_contentType = FPSTR(CONTENT_TYPE_JSON);
if(isArray)
_root = _jsonBuffer.createNestedArray();
else
Expand Down
4 changes: 2 additions & 2 deletions wled00/util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -207,15 +207,15 @@ bool isAsterisksOnly(const char* str, byte maxLen)


//threading/network callback details: https://github.com/Aircoookie/WLED/pull/2336#discussion_r762276994
bool requestJSONBufferLock(uint8_t module)
bool requestJSONBufferLock(uint8_t module, bool yield_ok)
{
if (pDoc == nullptr) {
DEBUG_PRINTLN(F("ERROR: JSON buffer not allocated!"));
return false;
}
unsigned long now = millis();

while (jsonBufferLock && millis()-now < 100) delay(1); // wait for fraction for buffer lock
while (jsonBufferLock && yield_ok && (millis()-now < 100)) delay(1); // wait for fraction for buffer lock

if (jsonBufferLock) {
DEBUG_PRINT(F("ERROR: Locking JSON buffer failed! (still locked by "));
Expand Down
5 changes: 3 additions & 2 deletions wled00/wled.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -157,11 +157,11 @@ void WLED::loop()
if (millis() - heapTime > 15000) {
uint32_t heap = ESP.getFreeHeap();
if (heap < MIN_HEAP_SIZE && lastHeap < MIN_HEAP_SIZE) {
DEBUG_PRINT(F("Heap too low! ")); DEBUG_PRINTLN(heap);
DEBUG_PRINT(F("\n***** Heap too low, forcing reconnect! ")); DEBUG_PRINTLN(heap);
forceReconnect = true;
strip.resetSegments(); // remove all but one segments from memory
} else if (heap < MIN_HEAP_SIZE) {
DEBUG_PRINTLN(F("Heap low, purging segments."));
DEBUG_PRINTLN(F("\n***** Heap low, purging segments."));
strip.purgeSegments();
}
lastHeap = heap;
Expand Down Expand Up @@ -265,6 +265,7 @@ void WLED::loop()
DEBUG_PRINT(F("Strip time[ms]: ")); DEBUG_PRINT(avgStripMillis/loops); DEBUG_PRINT("/"); DEBUG_PRINTLN(maxStripMillis);
}
strip.printSize();
server.dumpStatus();
loops = 0;
maxLoopMillis = 0;
maxUsermodMillis = 0;
Expand Down
2 changes: 1 addition & 1 deletion wled00/wled.h
Original file line number Diff line number Diff line change
Expand Up @@ -713,7 +713,7 @@ WLED_GLOBAL bool ledStatusState _INIT(false); // the current LED state
#endif

// server library objects
WLED_GLOBAL AsyncWebServer server _INIT_N(((80)));
WLED_GLOBAL AsyncWebServer server _INIT_N(((80, WLED_MAX_CONCURRENT_REQUESTS, WLED_MAX_QUEUED_REQUESTS)));
#ifdef WLED_ENABLE_WEBSOCKETS
WLED_GLOBAL AsyncWebSocket ws _INIT_N((("/ws")));
#endif
Expand Down
53 changes: 28 additions & 25 deletions wled00/wled_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ static const char s_unlock_ota [] PROGMEM = "Please unlock OTA in security setti
static const char s_unlock_cfg [] PROGMEM = "Please unlock settings using PIN code!";
static const char s_notimplemented[] PROGMEM = "Not implemented";
static const char s_accessdenied[] PROGMEM = "Access Denied";
static const char s_javascript[] PROGMEM = "application/javascript";
static const char s_json[] PROGMEM = "application/json";
static const char s_html[] PROGMEM = "text/html";
static const char s_plain[] PROGMEM = "text/plain";
static const char s_css[] PROGMEM = "text/css";
static const char* s_javascript = CONTENT_TYPE_JAVASCRIPT;
static const char* s_json = CONTENT_TYPE_JSON;
static const char* s_html = CONTENT_TYPE_HTML;
static const char* s_plain = CONTENT_TYPE_PLAIN;
static const char* s_css = CONTENT_TYPE_CSS;

//Is this an IP?
static bool isIp(String str) {
Expand Down Expand Up @@ -234,54 +234,57 @@ void initServer()

#ifdef WLED_ENABLE_WEBSOCKETS
#ifndef WLED_DISABLE_2D
server.on(SET_F("/liveview2D"), HTTP_GET, [](AsyncWebServerRequest *request) {
server.on(F("/liveview2D"), HTTP_GET, [](AsyncWebServerRequest *request) {
handleStaticContent(request, "", 200, FPSTR(s_html), PAGE_liveviewws2D, PAGE_liveviewws2D_length);
});
#endif
#endif
server.on(SET_F("/liveview"), HTTP_GET, [](AsyncWebServerRequest *request) {
server.on(F("/liveview"), HTTP_GET, [](AsyncWebServerRequest *request) {
handleStaticContent(request, "", 200, FPSTR(s_html), PAGE_liveview, PAGE_liveview_length);
});

//settings page
server.on(SET_F("/settings"), HTTP_GET, [](AsyncWebServerRequest *request){
server.on(F("/settings"), HTTP_GET, [](AsyncWebServerRequest *request){
serveSettings(request);
});

// "/settings/settings.js&p=x" request also handled by serveSettings()

server.on(SET_F("/style.css"), HTTP_GET, [](AsyncWebServerRequest *request) {
handleStaticContent(request, F("/style.css"), 200, FPSTR(s_css), PAGE_settingsCss, PAGE_settingsCss_length);
const static char STYLE_CSS[] PROGMEM = "/style.css";
server.on(FPSTR(STYLE_CSS), HTTP_GET, [](AsyncWebServerRequest *request) {
handleStaticContent(request, FPSTR(STYLE_CSS), 200, FPSTR(s_css), PAGE_settingsCss, PAGE_settingsCss_length);
});

server.on(SET_F("/favicon.ico"), HTTP_GET, [](AsyncWebServerRequest *request) {
handleStaticContent(request, F("/favicon.ico"), 200, F("image/x-icon"), favicon, favicon_length, false);
const static char FAVICON_ICO[] PROGMEM = "/favicon.ico";
server.on(FPSTR(FAVICON_ICO), HTTP_GET, [](AsyncWebServerRequest *request) {
handleStaticContent(request, FPSTR(FAVICON_ICO), 200, FPSTR(CONTENT_TYPE_XICON), favicon, favicon_length, false);
});

server.on(SET_F("/skin.css"), HTTP_GET, [](AsyncWebServerRequest *request) {
if (handleFileRead(request, F("/skin.css"))) return;
const static char SKIN_CSS[] PROGMEM = "/skin.css";
server.on(FPSTR(SKIN_CSS), HTTP_GET, [](AsyncWebServerRequest *request) {
if (handleFileRead(request, FPSTR(SKIN_CSS))) return;
AsyncWebServerResponse *response = request->beginResponse(200, FPSTR(s_css));
request->send(response);
});

server.on(SET_F("/welcome"), HTTP_GET, [](AsyncWebServerRequest *request){
server.on(F("/welcome"), HTTP_GET, [](AsyncWebServerRequest *request){
serveSettings(request);
});

server.on(SET_F("/reset"), HTTP_GET, [](AsyncWebServerRequest *request){
server.on(F("/reset"), HTTP_GET, [](AsyncWebServerRequest *request){
serveMessage(request, 200,F("Rebooting now..."),F("Please wait ~10 seconds..."),129);
doReboot = true;
});

server.on(SET_F("/settings"), HTTP_POST, [](AsyncWebServerRequest *request){
server.on(F("/settings"), HTTP_POST, [](AsyncWebServerRequest *request){
serveSettings(request, true);
});

server.on(SET_F("/json"), HTTP_GET, [](AsyncWebServerRequest *request){
const static char JSON[] PROGMEM = "/json";
server.on(FPSTR(JSON), HTTP_GET, [](AsyncWebServerRequest *request){
serveJson(request);
});

AsyncCallbackJsonWebHandler* handler = new AsyncCallbackJsonWebHandler(F("/json"), [](AsyncWebServerRequest *request) {
AsyncCallbackJsonWebHandler* handler = new AsyncCallbackJsonWebHandler(FPSTR(JSON), [](AsyncWebServerRequest *request) {
bool verboseResponse = false;
bool isConfig = false;

Expand Down Expand Up @@ -333,15 +336,15 @@ void initServer()
}, JSON_BUFFER_SIZE);
server.addHandler(handler);

server.on(SET_F("/version"), HTTP_GET, [](AsyncWebServerRequest *request){
server.on(F("/version"), HTTP_GET, [](AsyncWebServerRequest *request){
request->send(200, FPSTR(s_plain), (String)VERSION);
});

server.on(SET_F("/uptime"), HTTP_GET, [](AsyncWebServerRequest *request){
server.on(F("/uptime"), HTTP_GET, [](AsyncWebServerRequest *request){
request->send(200, FPSTR(s_plain), (String)millis());
});

server.on(SET_F("/freeheap"), HTTP_GET, [](AsyncWebServerRequest *request){
server.on(F("/freeheap"), HTTP_GET, [](AsyncWebServerRequest *request){
request->send(200, FPSTR(s_plain), (String)ESP.getFreeHeap());
});

Expand All @@ -351,11 +354,11 @@ void initServer()
});
#endif

server.on(SET_F("/teapot"), HTTP_GET, [](AsyncWebServerRequest *request){
server.on(F("/teapot"), HTTP_GET, [](AsyncWebServerRequest *request){
serveMessage(request, 418, F("418. I'm a teapot."), F("(Tangible Embedded Advanced Project Of Twinkling)"), 254);
});

server.on(SET_F("/upload"), HTTP_POST, [](AsyncWebServerRequest *request) {},
server.on(F("/upload"), HTTP_POST, [](AsyncWebServerRequest *request) {},
[](AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data,
size_t len, bool final) {handleUpload(request, filename, index, data, len, final);}
);
Expand Down
Loading
Loading