+
+const char* htmlContent PROGMEM = R"(
+
+
+
+ Sample HTML
+
+
+ Hello, World!
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
+ rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
+ arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
+ accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
+ Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
+ dapibus elit, id varius sem dui id lacus.
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
+ rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
+ arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
+ accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
+ Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
+ dapibus elit, id varius sem dui id lacus.
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
+ rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
+ arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
+ accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
+ Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
+ dapibus elit, id varius sem dui id lacus.
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
+ rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
+ arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
+ accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
+ Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
+ dapibus elit, id varius sem dui id lacus.
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
+ rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
+ arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
+ accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
+ Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
+ dapibus elit, id varius sem dui id lacus.
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
+ rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
+ arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
+ accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
+ Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
+ dapibus elit, id varius sem dui id lacus.
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
+ rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
+ arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
+ accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
+ Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
+ dapibus elit, id varius sem dui id lacus.
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
+ rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
+ arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
+ accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
+ Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
+ dapibus elit, id varius sem dui id lacus.
+
+
+)";
+
+const char* staticContent PROGMEM = R"(
+
+
+
+ Sample HTML
+
+
+ Hello, %IP%
+
+
+)";
+
+AsyncWebServer server(80);
+AsyncEventSource events("/events");
+
+/////////////////////////////////////////////////////////////////////////////////////////////////////
+
+const char* PARAM_MESSAGE PROGMEM = "message";
+const char* SSE_HTLM PROGMEM = R"(
+
+
+
+ Server-Sent Events
+
+
+
+ Open your browser console!
+
+
+)";
+
+static const char* SSE_MSG = R"(Alice felt that this could not be denied, so she tried another question. 'What sort of people live about here?' 'In THAT direction,' the Cat said, waving its right paw round, 'lives a Hatter: and in THAT direction,' waving the other paw, 'lives a March Hare. Visit either you like: they're both mad.'
+'But I don't want to go among mad people,' Alice remarked. 'Oh, you can't help that,' said the Cat: 'we're all mad here. I'm mad. You're mad.' 'How do you know I'm mad?' said Alice.
+'You must be,' said the Cat, `or you wouldn't have come here.' Alice didn't think that proved it at all; however, she went on 'And how do you know that you're mad?' 'To begin with,' said the Cat, 'a dog's not mad. You grant that?'
+)";
+
+void notFound(AsyncWebServerRequest* request) {
+ request->send(404, "text/plain", "Not found");
+}
+
+static const char characters[] = "0123456789ABCDEF";
+static size_t charactersIndex = 0;
+
+void setup() {
+
+ Serial.begin(115200);
+
+#ifndef CONFIG_IDF_TARGET_ESP32H2
+ /*
+ WiFi.mode(WIFI_STA);
+ WiFi.begin("SSID", "passwd");
+ if (WiFi.waitForConnectResult() != WL_CONNECTED) {
+ Serial.printf("WiFi Failed!\n");
+ return;
+ }
+ Serial.print("IP Address: ");
+ Serial.println(WiFi.localIP());
+ */
+
+ WiFi.mode(WIFI_AP);
+ WiFi.softAP("esp-captive");
+#endif
+
+ server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) {
+ request->send(200, "text/html", staticContent);
+ });
+
+ events.onConnect([](AsyncEventSourceClient* client) {
+ if (client->lastId()) {
+ Serial.printf("SSE Client reconnected! Last message ID that it gat is: %" PRIu32 "\n", client->lastId());
+ }
+ client->send("hello!", NULL, millis(), 1000);
+ });
+
+ server.on("/sse", HTTP_GET, [](AsyncWebServerRequest* request) {
+ request->send(200, "text/html", SSE_HTLM);
+ });
+
+ // go to http://192.168.4.1/sse
+ server.addHandler(&events);
+
+ server.onNotFound(notFound);
+
+ server.begin();
+}
+
+uint32_t lastSSE = 0;
+uint32_t deltaSSE = 25;
+uint32_t messagesSSE = 4; // how many messages to q each time
+uint32_t sse_disc{0}, sse_enq{0}, sse_penq{0}, sse_second{0};
+
+AsyncEventSource::SendStatus enqueue() {
+ AsyncEventSource::SendStatus state = events.send(SSE_MSG, "message");
+ if (state == AsyncEventSource::SendStatus::DISCARDED)
+ ++sse_disc;
+ else if (state == AsyncEventSource::SendStatus::ENQUEUED) {
+ ++sse_enq;
+ } else
+ ++sse_penq;
+
+ return state;
+}
+
+void loop() {
+ uint32_t now = millis();
+ if (now - lastSSE >= deltaSSE) {
+ // enqueue messages
+ for (uint32_t i = 0; i != messagesSSE; ++i) {
+ auto err = enqueue();
+ if (err == AsyncEventSource::SendStatus::DISCARDED || err == AsyncEventSource::SendStatus::PARTIALLY_ENQUEUED) {
+ // throttle messaging a bit
+ lastSSE = now + deltaSSE;
+ break;
+ }
+ }
+
+ lastSSE = millis();
+ }
+
+ if (now - sse_second > 1000) {
+ String s;
+ s.reserve(100);
+ s = "Ping:";
+ s += now / 1000;
+ s += " clients:";
+ s += events.count();
+ s += " disc:";
+ s += sse_disc;
+ s += " enq:";
+ s += sse_enq;
+ s += " partial:";
+ s += sse_penq;
+ s += " avg wait:";
+ s += events.avgPacketsWaiting();
+ s += " heap:";
+ s += ESP.getFreeHeap() / 1024;
+
+ events.send(s, "heartbeat", now);
+ Serial.println();
+ Serial.println(s);
+
+ // if we see discards or partial enqueues, let's decrease message rate, else - increase. So that we can come to a max sustained message rate
+ if (sse_disc || sse_penq)
+ ++deltaSSE;
+ else if (deltaSSE > 5)
+ --deltaSSE;
+
+ sse_disc = sse_enq = sse_penq = 0;
+ sse_second = now;
+ }
+}
diff --git a/examples/SimpleServer/SimpleServer.ino b/examples/SimpleServer/SimpleServer.ino
new file mode 100644
index 000000000..8a2ccfe34
--- /dev/null
+++ b/examples/SimpleServer/SimpleServer.ino
@@ -0,0 +1,794 @@
+//
+// A simple server implementation showing how to:
+// * serve static messages
+// * read GET and POST parameters
+// * handle missing pages / 404s
+//
+
+#include
+#ifdef ESP32
+ #include
+ #include
+#elif defined(ESP8266)
+ #include
+ #include
+#elif defined(TARGET_RP2040)
+ #include
+ #include
+#endif
+
+#include
+
+#if __has_include("ArduinoJson.h")
+ #include
+ #include
+ #include
+#endif
+
+#include
+
+const char* htmlContent PROGMEM = R"(
+
+
+
+ Sample HTML
+
+
+ Hello, World!
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
+ rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
+ arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
+ accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
+ Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
+ dapibus elit, id varius sem dui id lacus.
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
+ rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
+ arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
+ accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
+ Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
+ dapibus elit, id varius sem dui id lacus.
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
+ rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
+ arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
+ accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
+ Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
+ dapibus elit, id varius sem dui id lacus.
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
+ rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
+ arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
+ accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
+ Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
+ dapibus elit, id varius sem dui id lacus.
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
+ rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
+ arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
+ accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
+ Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
+ dapibus elit, id varius sem dui id lacus.
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
+ rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
+ arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
+ accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
+ Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
+ dapibus elit, id varius sem dui id lacus.
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
+ rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
+ arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
+ accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
+ Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
+ dapibus elit, id varius sem dui id lacus.
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
+ rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
+ arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
+ accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
+ Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
+ dapibus elit, id varius sem dui id lacus.
+
+
+)";
+
+const size_t htmlContentLength = strlen_P(htmlContent);
+
+const char* staticContent PROGMEM = R"(
+
+
+
+ Sample HTML
+
+
+ Hello, %IP%
+
+
+)";
+
+AsyncWebServer server(80);
+AsyncEventSource events("/events");
+AsyncWebSocket ws("/ws");
+
+/////////////////////////////////////////////////////////////////////////////////////////////////////
+// Middlewares
+/////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// log incoming requests
+AsyncLoggingMiddleware requestLogger;
+
+// CORS
+AsyncCorsMiddleware cors;
+
+// maximum 5 requests per 10 seconds
+AsyncRateLimitMiddleware rateLimit;
+
+// filter out specific headers from the incoming request
+AsyncHeaderFilterMiddleware headerFilter;
+
+// remove all headers from the incoming request except the ones provided in the constructor
+AsyncHeaderFreeMiddleware headerFree;
+
+// basicAuth
+AsyncAuthenticationMiddleware basicAuth;
+AsyncAuthenticationMiddleware basicAuthHash;
+
+// simple digest authentication
+AsyncAuthenticationMiddleware digestAuth;
+AsyncAuthenticationMiddleware digestAuthHash;
+
+// complex authentication which adds request attributes for the next middlewares and handler
+AsyncMiddlewareFunction complexAuth([](AsyncWebServerRequest* request, ArMiddlewareNext next) {
+ if (!request->authenticate("user", "password")) {
+ return request->requestAuthentication();
+ }
+ request->setAttribute("user", "Mathieu");
+ request->setAttribute("role", "staff");
+
+ next();
+
+ request->getResponse()->addHeader("X-Rate-Limit", "200");
+});
+
+AsyncAuthorizationMiddleware authz([](AsyncWebServerRequest* request) { return request->getAttribute("role") == "staff"; });
+
+int wsClients = 0;
+
+/////////////////////////////////////////////////////////////////////////////////////////////////////
+
+const char* PARAM_MESSAGE PROGMEM = "message";
+const char* SSE_HTLM PROGMEM = R"(
+
+
+
+ Server-Sent Events
+
+
+
+ Open your browser console!
+
+
+)";
+
+void notFound(AsyncWebServerRequest* request) {
+ request->send(404, "text/plain", "Not found");
+}
+
+#if __has_include("ArduinoJson.h")
+AsyncCallbackJsonWebHandler* jsonHandler = new AsyncCallbackJsonWebHandler("/json2");
+AsyncCallbackMessagePackWebHandler* msgPackHandler = new AsyncCallbackMessagePackWebHandler("/msgpack2");
+#endif
+
+static const char characters[] = "0123456789ABCDEF";
+static size_t charactersIndex = 0;
+
+void setup() {
+
+ Serial.begin(115200);
+
+#ifndef CONFIG_IDF_TARGET_ESP32H2
+ // WiFi.mode(WIFI_STA);
+ // WiFi.begin("YOUR_SSID", "YOUR_PASSWORD");
+ // if (WiFi.waitForConnectResult() != WL_CONNECTED) {
+ // Serial.printf("WiFi Failed!\n");
+ // return;
+ // }
+ // Serial.print("IP Address: ");
+ // Serial.println(WiFi.localIP());
+
+ WiFi.mode(WIFI_AP);
+ WiFi.softAP("esp-captive");
+#endif
+
+#ifdef ESP32
+ LittleFS.begin(true);
+#else
+ LittleFS.begin();
+#endif
+
+ {
+ File f = LittleFS.open("/index.txt", "w");
+ if (f) {
+ for (size_t c = 0; c < sizeof(characters); c++) {
+ for (size_t i = 0; i < 1024; i++) {
+ f.print(characters[c]);
+ }
+ }
+ f.close();
+ }
+ }
+
+ {
+ File f = LittleFS.open("/index.html", "w");
+ if (f) {
+ f.print(staticContent);
+ f.close();
+ }
+ }
+
+ // curl -v -X GET http://192.168.4.1/handler-not-sending-response
+ server.on("/handler-not-sending-response", HTTP_GET, [](AsyncWebServerRequest* request) {
+ // handler forgot to send a response to the client => 501 Not Implemented
+ });
+
+ // This is possible to replace a response.
+ // the previous one will be deleted.
+ // response sending happens when the handler returns.
+ // curl -v -X GET http://192.168.4.1/replace
+ server.on("/replace", HTTP_GET, [](AsyncWebServerRequest* request) {
+ request->send(200, "text/plain", "Hello, world");
+ // oups! finally we want to send a different response
+ request->send(400, "text/plain", "validation error");
+#ifndef TARGET_RP2040
+ Serial.printf("Free heap: %" PRIu32 "\n", ESP.getFreeHeap());
+#endif
+ });
+
+ ///////////////////////////////////////////////////////////////////////
+ // Request header manipulations
+ ///////////////////////////////////////////////////////////////////////
+
+ // curl -v -X GET -H "x-remove-me: value" http://192.168.4.1/headers
+ server.on("/headers", HTTP_GET, [](AsyncWebServerRequest* request) {
+ Serial.printf("Request Headers:\n");
+ for (auto& h : request->getHeaders())
+ Serial.printf("Request Header: %s = %s\n", h.name().c_str(), h.value().c_str());
+
+ // remove x-remove-me header
+ request->removeHeader("x-remove-me");
+ Serial.printf("Request Headers:\n");
+ for (auto& h : request->getHeaders())
+ Serial.printf("Request Header: %s = %s\n", h.name().c_str(), h.value().c_str());
+
+ std::vector headers;
+ request->getHeaderNames(headers);
+ for (auto& h : headers)
+ Serial.printf("Request Header Name: %s\n", h);
+
+ request->send(200);
+ });
+
+ ///////////////////////////////////////////////////////////////////////
+ // Middlewares at server level (will apply to all requests)
+ ///////////////////////////////////////////////////////////////////////
+
+ requestLogger.setOutput(Serial);
+
+ basicAuth.setUsername("admin");
+ basicAuth.setPassword("admin");
+ basicAuth.setRealm("MyApp");
+ basicAuth.setAuthFailureMessage("Authentication failed");
+ basicAuth.setAuthType(AsyncAuthType::AUTH_BASIC);
+ basicAuth.generateHash();
+
+ basicAuthHash.setUsername("admin");
+ basicAuthHash.setPasswordHash("YWRtaW46YWRtaW4="); // BASE64(admin:admin)
+ basicAuthHash.setRealm("MyApp");
+ basicAuthHash.setAuthFailureMessage("Authentication failed");
+ basicAuthHash.setAuthType(AsyncAuthType::AUTH_BASIC);
+
+ digestAuth.setUsername("admin");
+ digestAuth.setPassword("admin");
+ digestAuth.setRealm("MyApp");
+ digestAuth.setAuthFailureMessage("Authentication failed");
+ digestAuth.setAuthType(AsyncAuthType::AUTH_DIGEST);
+ digestAuth.generateHash();
+
+ digestAuthHash.setUsername("admin");
+ digestAuthHash.setPasswordHash("f499b71f9a36d838b79268e145e132f7"); // MD5(user:realm:pass)
+ digestAuthHash.setRealm("MyApp");
+ digestAuthHash.setAuthFailureMessage("Authentication failed");
+ digestAuthHash.setAuthType(AsyncAuthType::AUTH_DIGEST);
+
+ rateLimit.setMaxRequests(5);
+ rateLimit.setWindowSize(10);
+
+ headerFilter.filter("X-Remove-Me");
+ headerFree.keep("X-Keep-Me");
+ headerFree.keep("host");
+
+ cors.setOrigin("http://192.168.4.1");
+ cors.setMethods("POST, GET, OPTIONS, DELETE");
+ cors.setHeaders("X-Custom-Header");
+ cors.setAllowCredentials(false);
+ cors.setMaxAge(600);
+
+#ifndef PERF_TEST
+ // global middleware
+ server.addMiddleware(&requestLogger);
+ server.addMiddlewares({&rateLimit, &cors, &headerFilter});
+#endif
+
+ // Test CORS preflight request
+ // curl -v -X OPTIONS -H "origin: http://192.168.4.1" http://192.168.4.1/middleware/cors
+ server.on("/middleware/cors", HTTP_GET, [](AsyncWebServerRequest* request) {
+ request->send(200, "text/plain", "Hello, world!");
+ });
+
+ // curl -v -X GET -H "x-remove-me: value" http://192.168.4.1/middleware/test-header-filter
+ // - requestLogger will log the incoming headers (including x-remove-me)
+ // - headerFilter will remove x-remove-me header
+ // - handler will log the remaining headers
+ server.on("/middleware/test-header-filter", HTTP_GET, [](AsyncWebServerRequest* request) {
+ for (auto& h : request->getHeaders())
+ Serial.printf("Request Header: %s = %s\n", h.name().c_str(), h.value().c_str());
+ request->send(200);
+ });
+
+ // curl -v -X GET -H "x-keep-me: value" http://192.168.4.1/middleware/test-header-free
+ // - requestLogger will log the incoming headers (including x-keep-me)
+ // - headerFree will remove all headers except x-keep-me and host
+ // - handler will log the remaining headers (x-keep-me and host)
+ server.on("/middleware/test-header-free", HTTP_GET, [](AsyncWebServerRequest* request) {
+ for (auto& h : request->getHeaders())
+ Serial.printf("Request Header: %s = %s\n", h.name().c_str(), h.value().c_str());
+ request->send(200);
+ })
+ .addMiddleware(&headerFree);
+
+ // basic authentication method
+ // curl -v -X GET -H "origin: http://192.168.4.1" -u admin:admin http://192.168.4.1/middleware/auth-basic
+ server.on("/middleware/auth-basic", HTTP_GET, [](AsyncWebServerRequest* request) {
+ request->send(200, "text/plain", "Hello, world!");
+ })
+ .addMiddleware(&basicAuth);
+
+ // basic authentication method with hash
+ // curl -v -X GET -H "origin: http://192.168.4.1" -u admin:admin http://192.168.4.1/middleware/auth-basic-hash
+ server.on("/middleware/auth-basic-hash", HTTP_GET, [](AsyncWebServerRequest* request) {
+ request->send(200, "text/plain", "Hello, world!");
+ })
+ .addMiddleware(&basicAuthHash);
+
+ // digest authentication
+ // curl -v -X GET -H "origin: http://192.168.4.1" -u admin:admin --digest http://192.168.4.1/middleware/auth-digest
+ server.on("/middleware/auth-digest", HTTP_GET, [](AsyncWebServerRequest* request) {
+ request->send(200, "text/plain", "Hello, world!");
+ })
+ .addMiddleware(&digestAuth);
+
+ // digest authentication with hash
+ // curl -v -X GET -H "origin: http://192.168.4.1" -u admin:admin --digest http://192.168.4.1/middleware/auth-digest-hash
+ server.on("/middleware/auth-digest-hash", HTTP_GET, [](AsyncWebServerRequest* request) {
+ request->send(200, "text/plain", "Hello, world!");
+ })
+ .addMiddleware(&digestAuthHash);
+
+ // test digest auth with cors
+ // curl -v -X GET -H "origin: http://192.168.4.1" --digest -u user:password http://192.168.4.1/middleware/auth-custom
+ server.on("/middleware/auth-custom", HTTP_GET, [](AsyncWebServerRequest* request) {
+ String buffer = "Hello ";
+ buffer.concat(request->getAttribute("user"));
+ buffer.concat(" with role: ");
+ buffer.concat(request->getAttribute("role"));
+ request->send(200, "text/plain", buffer);
+ })
+ .addMiddlewares({&complexAuth, &authz});
+
+ ///////////////////////////////////////////////////////////////////////
+
+ // curl -v -X GET -H "origin: http://192.168.4.1" http://192.168.4.1/redirect
+ // curl -v -X POST -H "origin: http://192.168.4.1" http://192.168.4.1/redirect
+ server.on("/redirect", HTTP_GET | HTTP_POST, [](AsyncWebServerRequest* request) {
+ request->redirect("/");
+ });
+
+ // PERF TEST:
+ // > brew install autocannon
+ // > autocannon -c 10 -w 10 -d 20 http://192.168.4.1
+ // > autocannon -c 16 -w 16 -d 20 http://192.168.4.1
+ server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) {
+ request->send(200, "text/html", htmlContent);
+ });
+
+ // curl -v -X GET http://192.168.4.1/index.txt
+ server.serveStatic("/index.txt", LittleFS, "/index.txt");
+
+ // curl -v -X GET http://192.168.4.1/index-private.txt
+ server.serveStatic("/index-private.txt", LittleFS, "/index.txt").setAuthentication("admin", "admin");
+
+ // ServeStatic static is used to serve static output which never changes over time.
+ // This special endpoints automatyically adds caching headers.
+ // If a template processor is used, it must enure that the outputed content will always be the ame over time and never changes.
+ // Otherwise, do not use serveStatic.
+ // Example below: IP never changes.
+ // curl -v -X GET http://192.168.4.1/index-static.html
+ server.serveStatic("/index-static.html", LittleFS, "/index.html").setTemplateProcessor([](const String& var) -> String {
+ if (var == "IP") {
+ // for CI, commented out since H2 board doesn ot support WiFi
+ // return WiFi.localIP().toString();
+ // return WiFi.softAPIP().toString();
+ return "127.0.0..1";
+ }
+ return emptyString;
+ });
+
+ // to serve a template with dynamic content (output changes over time), use normal
+ // Example below: content changes over tinme do not use serveStatic.
+ // curl -v -X GET http://192.168.4.1/index-dynamic.html
+ server.on("/index-dynamic.html", HTTP_GET, [](AsyncWebServerRequest* request) {
+ request->send(LittleFS, "/index.html", "text/html", false, [](const String& var) -> String {
+ if (var == "IP")
+ return String(random(0, 1000));
+ return emptyString;
+ });
+ });
+
+ // Issue #14: assert failed: tcp_update_rcv_ann_wnd (needs help to test fix)
+ // > curl -v http://192.168.4.1/issue-14
+ pinMode(4, OUTPUT);
+ server.on("/issue-14", HTTP_GET, [](AsyncWebServerRequest* request) {
+ digitalWrite(4, HIGH);
+ request->send(LittleFS, "/index.txt", "text/pain");
+ delay(500);
+ digitalWrite(4, LOW);
+ });
+
+ /*
+ Chunked encoding test: sends 16k of characters.
+ ❯ curl -N -v -X GET -H "origin: http://192.168.4.1" http://192.168.4.1/chunk
+ */
+ server.on("/chunk", HTTP_HEAD | HTTP_GET, [](AsyncWebServerRequest* request) {
+ AsyncWebServerResponse* response = request->beginChunkedResponse("text/html", [](uint8_t* buffer, size_t maxLen, size_t index) -> size_t {
+ if (index >= 16384)
+ return 0;
+ memset(buffer, characters[charactersIndex], maxLen);
+ charactersIndex = (charactersIndex + 1) % sizeof(characters);
+ return maxLen;
+ });
+ request->send(response);
+ });
+
+ // curl -N -v -X GET http://192.168.4.1/chunked.html --output -
+ // curl -N -v -X GET -H "if-none-match: 4272" http://192.168.4.1/chunked.html --output -
+ server.on("/chunked.html", HTTP_GET, [](AsyncWebServerRequest* request) {
+ String len = String(htmlContentLength);
+
+ if (request->header(asyncsrv::T_INM) == len) {
+ request->send(304);
+ return;
+ }
+
+ AsyncWebServerResponse* response = request->beginChunkedResponse("text/html", [](uint8_t* buffer, size_t maxLen, size_t index) -> size_t {
+ Serial.printf("%u / %u\n", index, htmlContentLength);
+
+ // finished ?
+ if (htmlContentLength <= index) {
+ Serial.println("finished");
+ return 0;
+ }
+
+ // serve a maximum of 1024 or maxLen bytes of the remaining content
+ const int chunkSize = min((size_t)1024, min(maxLen, htmlContentLength - index));
+ Serial.printf("sending: %u\n", chunkSize);
+
+ memcpy(buffer, htmlContent + index, chunkSize);
+
+ return chunkSize;
+ });
+
+ response->addHeader(asyncsrv::T_Cache_Control, "public,max-age=60");
+ response->addHeader(asyncsrv::T_ETag, len);
+
+ request->send(response);
+ });
+
+ // time curl -N -v -G -d 'd=3000' -d 'l=10000' http://192.168.4.1/slow.html --output -
+ server.on("/slow.html", HTTP_GET, [](AsyncWebServerRequest* request) {
+ uint32_t d = request->getParam("d")->value().toInt();
+ uint32_t l = request->getParam("l")->value().toInt();
+ Serial.printf("d = %" PRIu32 ", l = %" PRIu32 "\n", d, l);
+ AsyncWebServerResponse* response = request->beginChunkedResponse("text/html", [d, l](uint8_t* buffer, size_t maxLen, size_t index) -> size_t {
+ Serial.printf("%u\n", index);
+ // finished ?
+ if (index >= l)
+ return 0;
+
+ // slow down the task by 2 seconds
+ // to simulate some heavy processing, like SD card reading
+ delay(d);
+
+ memset(buffer, characters[charactersIndex], 256);
+ charactersIndex = (charactersIndex + 1) % sizeof(characters);
+ return 256;
+ });
+
+ request->send(response);
+ });
+
+ /*
+ ❯ curl -I -X HEAD http://192.168.4.1/download
+ HTTP/1.1 200 OK
+ Content-Length: 1024
+ Content-Type: application/octet-stream
+ Connection: close
+ Accept-Ranges: bytes
+ */
+ // Ref: https://github.com/mathieucarbou/ESPAsyncWebServer/pull/80
+ server.on("/download", HTTP_HEAD | HTTP_GET, [](AsyncWebServerRequest* request) {
+ if (request->method() == HTTP_HEAD) {
+ AsyncWebServerResponse* response = request->beginResponse(200, "application/octet-stream");
+ response->addHeader(asyncsrv::T_Accept_Ranges, "bytes");
+ response->addHeader(asyncsrv::T_Content_Length, 10);
+ response->setContentLength(1024); // overrides previous one
+ response->addHeader(asyncsrv::T_Content_Type, "foo");
+ response->setContentType("application/octet-stream"); // overrides previous one
+ // ...
+ request->send(response);
+ } else {
+ // ...
+ }
+ });
+
+ // Send a GET request to /get?message=
+ server.on("/get", HTTP_GET, [](AsyncWebServerRequest* request) {
+ String message;
+ if (request->hasParam(PARAM_MESSAGE)) {
+ message = request->getParam(PARAM_MESSAGE)->value();
+ } else {
+ message = "No message sent";
+ }
+ request->send(200, "text/plain", "Hello, GET: " + message);
+ });
+
+ // Send a POST request to /post with a form field message set to
+ server.on("/post", HTTP_POST, [](AsyncWebServerRequest* request) {
+ String message;
+ if (request->hasParam(PARAM_MESSAGE, true)) {
+ message = request->getParam(PARAM_MESSAGE, true)->value();
+ } else {
+ message = "No message sent";
+ }
+ request->send(200, "text/plain", "Hello, POST: " + message);
+ });
+
+#if __has_include("ArduinoJson.h")
+ // JSON
+
+ // sends JSON
+ // curl -v -X GET http://192.168.4.1/json1
+ server.on("/json1", HTTP_GET, [](AsyncWebServerRequest* request) {
+ AsyncJsonResponse* response = new AsyncJsonResponse();
+ JsonObject root = response->getRoot().to();
+ root["hello"] = "world";
+ response->setLength();
+ request->send(response);
+ });
+
+ // curl -v -X GET http://192.168.4.1/json2
+ server.on("/json2", HTTP_GET, [](AsyncWebServerRequest* request) {
+ AsyncResponseStream* response = request->beginResponseStream("application/json");
+ JsonDocument doc;
+ JsonObject root = doc.to();
+ root["foo"] = "bar";
+ serializeJson(root, *response);
+ request->send(response);
+ });
+
+ // curl -v -X POST -H 'Content-Type: application/json' -d '{"name":"You"}' http://192.168.4.1/json2
+ // curl -v -X PUT -H 'Content-Type: application/json' -d '{"name":"You"}' http://192.168.4.1/json2
+ jsonHandler->setMethod(HTTP_POST | HTTP_PUT);
+ jsonHandler->onRequest([](AsyncWebServerRequest* request, JsonVariant& json) {
+ serializeJson(json, Serial);
+ AsyncJsonResponse* response = new AsyncJsonResponse();
+ JsonObject root = response->getRoot().to();
+ root["hello"] = json.as()["name"];
+ response->setLength();
+ request->send(response);
+ });
+
+ // MessagePack
+
+ // receives MessagePack and sends MessagePack
+ msgPackHandler->onRequest([](AsyncWebServerRequest* request, JsonVariant& json) {
+ // JsonObject jsonObj = json.as();
+ // ...
+
+ AsyncMessagePackResponse* response = new AsyncMessagePackResponse();
+ JsonObject root = response->getRoot().to();
+ root["hello"] = "world";
+ response->setLength();
+ request->send(response);
+ });
+
+ // sends MessagePack
+ server.on("/msgpack1", HTTP_GET, [](AsyncWebServerRequest* request) {
+ AsyncMessagePackResponse* response = new AsyncMessagePackResponse();
+ JsonObject root = response->getRoot().to();
+ root["hello"] = "world";
+ response->setLength();
+ request->send(response);
+ });
+#endif
+
+ events.onConnect([](AsyncEventSourceClient* client) {
+ if (client->lastId()) {
+ Serial.printf("SSE Client reconnected! Last message ID that it gat is: %" PRIu32 "\n", client->lastId());
+ }
+ client->send("hello!", NULL, millis(), 1000);
+ });
+
+ server.on("/sse", HTTP_GET, [](AsyncWebServerRequest* request) {
+ request->send(200, "text/html", SSE_HTLM);
+ });
+
+ ws.onEvent([](AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len) {
+ (void)len;
+ if (type == WS_EVT_CONNECT) {
+ wsClients++;
+ ws.textAll("new client connected");
+ Serial.println("ws connect");
+ client->setCloseClientOnQueueFull(false);
+ client->ping();
+ } else if (type == WS_EVT_DISCONNECT) {
+ wsClients--;
+ ws.textAll("client disconnected");
+ Serial.println("ws disconnect");
+ } else if (type == WS_EVT_ERROR) {
+ Serial.println("ws error");
+ } else if (type == WS_EVT_PONG) {
+ Serial.println("ws pong");
+ } else if (type == WS_EVT_DATA) {
+ AwsFrameInfo* info = (AwsFrameInfo*)arg;
+ String msg = "";
+ if (info->final && info->index == 0 && info->len == len) {
+ if (info->opcode == WS_TEXT) {
+ data[len] = 0;
+ Serial.printf("ws text: %s\n", (char*)data);
+ }
+ }
+ }
+ });
+
+ // SSS endpoints
+ // sends a message every 10 ms
+ //
+ // go to http://192.168.4.1/sse
+ // > curl -v -N -H "Accept: text/event-stream" http://192.168.4.1/events
+ //
+ // some perf tests:
+ // launch 16 concurrent workers for 30 seconds
+ // > for i in {1..10}; do ( count=$(gtimeout 30 curl -s -N -H "Accept: text/event-stream" http://192.168.4.1/events 2>&1 | grep -c "^data:"); echo "Total: $count events, $(echo "$count / 4" | bc -l) events / second" ) & done;
+ // > for i in {1..16}; do ( count=$(gtimeout 30 curl -s -N -H "Accept: text/event-stream" http://192.168.4.1/events 2>&1 | grep -c "^data:"); echo "Total: $count events, $(echo "$count / 4" | bc -l) events / second" ) & done;
+ //
+ // With AsyncTCP, with 16 workers: a lot of "Event message queue overflow: discard message", no crash
+ //
+ // Total: 1711 events, 427.75 events / second
+ // Total: 1711 events, 427.75 events / second
+ // Total: 1626 events, 406.50 events / second
+ // Total: 1562 events, 390.50 events / second
+ // Total: 1706 events, 426.50 events / second
+ // Total: 1659 events, 414.75 events / second
+ // Total: 1624 events, 406.00 events / second
+ // Total: 1706 events, 426.50 events / second
+ // Total: 1487 events, 371.75 events / second
+ // Total: 1573 events, 393.25 events / second
+ // Total: 1569 events, 392.25 events / second
+ // Total: 1559 events, 389.75 events / second
+ // Total: 1560 events, 390.00 events / second
+ // Total: 1562 events, 390.50 events / second
+ // Total: 1626 events, 406.50 events / second
+ //
+ // With AsyncTCP, with 10 workers:
+ //
+ // Total: 2038 events, 509.50 events / second
+ // Total: 2120 events, 530.00 events / second
+ // Total: 2119 events, 529.75 events / second
+ // Total: 2038 events, 509.50 events / second
+ // Total: 2037 events, 509.25 events / second
+ // Total: 2119 events, 529.75 events / second
+ // Total: 2119 events, 529.75 events / second
+ // Total: 2120 events, 530.00 events / second
+ // Total: 2038 events, 509.50 events / second
+ // Total: 2038 events, 509.50 events / second
+ //
+ // With AsyncTCPSock, with 16 workers: ESP32 CRASH !!!
+ //
+ // With AsyncTCPSock, with 10 workers:
+ //
+ // Total: 1242 events, 310.50 events / second
+ // Total: 1242 events, 310.50 events / second
+ // Total: 1242 events, 310.50 events / second
+ // Total: 1242 events, 310.50 events / second
+ // Total: 1181 events, 295.25 events / second
+ // Total: 1182 events, 295.50 events / second
+ // Total: 1240 events, 310.00 events / second
+ // Total: 1181 events, 295.25 events / second
+ // Total: 1181 events, 295.25 events / second
+ // Total: 1183 events, 295.75 events / second
+ //
+ server.addHandler(&events);
+
+ // Run in terminal 1: websocat ws://192.168.4.1/ws => stream data
+ // Run in terminal 2: websocat ws://192.168.4.1/ws => stream data
+ // Run in terminal 3: websocat ws://192.168.4.1/ws => should fail:
+ /*
+❯ websocat ws://192.168.4.1/ws
+websocat: WebSocketError: WebSocketError: Received unexpected status code (503 Service Unavailable)
+websocat: error running
+ */
+ server.addHandler(&ws).addMiddleware([](AsyncWebServerRequest* request, ArMiddlewareNext next) {
+ if (ws.count() > 2) {
+ // too many clients - answer back immediately and stop processing next middlewares and handler
+ request->send(503, "text/plain", "Server is busy");
+ } else {
+ // process next middleware and at the end the handler
+ next();
+ }
+ });
+
+ // Reset connection on HTTP request:
+ // for i in {1..20}; do curl -v -X GET https://192.168.4.1:80; done;
+ // The heap size should not decrease over time.
+
+#if __has_include("ArduinoJson.h")
+ server.addHandler(jsonHandler);
+ server.addHandler(msgPackHandler);
+#endif
+
+ server.onNotFound(notFound);
+
+ server.begin();
+}
+
+uint32_t lastSSE = 0;
+uint32_t deltaSSE = 10;
+
+uint32_t lastWS = 0;
+uint32_t deltaWS = 100;
+
+uint32_t lastHeap = 0;
+
+void loop() {
+ uint32_t now = millis();
+ if (now - lastSSE >= deltaSSE) {
+ events.send(String("ping-") + now, "heartbeat", now);
+ lastSSE = millis();
+ }
+ if (now - lastWS >= deltaWS) {
+ ws.printfAll("kp%.4f", (10.0 / 3.0));
+ // for (auto& client : ws.getClients()) {
+ // client.printf("kp%.4f", (10.0 / 3.0));
+ // }
+ lastWS = millis();
+ }
+#ifdef ESP32
+ if (now - lastHeap >= 2000) {
+ Serial.printf("Free heap: %" PRIu32 "\n", ESP.getFreeHeap());
+ lastHeap = now;
+ }
+#endif
+}
diff --git a/examples/StreamFiles/StreamConcat.h b/examples/StreamFiles/StreamConcat.h
new file mode 100644
index 000000000..c1e192769
--- /dev/null
+++ b/examples/StreamFiles/StreamConcat.h
@@ -0,0 +1,37 @@
+#pragma once
+
+#include
+
+class StreamConcat : public Stream {
+ public:
+ StreamConcat(Stream* s1, Stream* s2) : _s1(s1), _s2(s2) {}
+
+ size_t write(__unused const uint8_t* p, __unused size_t n) override { return 0; }
+ size_t write(__unused uint8_t c) override { return 0; }
+ void flush() override {}
+
+ int available() override { return _s1->available() + _s2->available(); }
+
+ int read() override {
+ int c = _s1->read();
+ return c != -1 ? c : _s2->read();
+ }
+
+#if defined(TARGET_RP2040)
+ size_t readBytes(char* buffer, size_t length) {
+#else
+ size_t readBytes(char* buffer, size_t length) override {
+#endif
+ size_t count = _s1->readBytes(buffer, length);
+ return count > 0 ? count : _s2->readBytes(buffer, length);
+ }
+
+ int peek() override {
+ int c = _s1->peek();
+ return c != -1 ? c : _s2->peek();
+ }
+
+ private:
+ Stream* _s1;
+ Stream* _s2;
+};
diff --git a/examples/StreamFiles/StreamFiles.ino b/examples/StreamFiles/StreamFiles.ino
new file mode 100644
index 000000000..a0d6b9f60
--- /dev/null
+++ b/examples/StreamFiles/StreamFiles.ino
@@ -0,0 +1,89 @@
+#include
+#include
+#ifdef ESP32
+ #include
+ #include
+#elif defined(ESP8266)
+ #include
+ #include
+#elif defined(TARGET_RP2040)
+ #include
+ #include
+#endif
+
+#include
+#include
+#include
+
+#include "StreamConcat.h"
+
+DNSServer dnsServer;
+AsyncWebServer server(80);
+
+void setup() {
+ Serial.begin(115200);
+
+ LittleFS.begin();
+
+#ifndef CONFIG_IDF_TARGET_ESP32H2
+ WiFi.mode(WIFI_AP);
+ WiFi.softAP("esp-captive");
+
+ dnsServer.start(53, "*", WiFi.softAPIP());
+#endif
+
+ File file1 = LittleFS.open("/header.html", "w");
+ file1.print("ESP Captive Portal");
+ file1.close();
+
+ File file2 = LittleFS.open("/body.html", "w");
+ file2.print("Welcome to ESP Captive Portal
");
+ file2.close();
+
+ File file3 = LittleFS.open("/footer.html", "w");
+ file3.print("");
+ file3.close();
+
+ server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) {
+ File header = LittleFS.open("/header.html", "r");
+ File body = LittleFS.open("/body.html", "r");
+ StreamConcat stream1(&header, &body);
+
+ StreamString content;
+#if defined(TARGET_RP2040)
+ content.printf("FreeHeap: %d", rp2040.getFreeHeap());
+#else
+ content.printf("FreeHeap: %" PRIu32, ESP.getFreeHeap());
+#endif
+ StreamConcat stream2 = StreamConcat(&stream1, &content);
+
+ File footer = LittleFS.open("/footer.html", "r");
+ StreamConcat stream3 = StreamConcat(&stream2, &footer);
+
+ request->send(stream3, "text/html", stream3.available());
+ header.close();
+ body.close();
+ footer.close();
+ });
+
+ server.onNotFound([](AsyncWebServerRequest* request) {
+ request->send(404, "text/plain", "Not found");
+ });
+
+ server.begin();
+}
+
+uint32_t last = 0;
+
+void loop() {
+ // dnsServer.processNextRequest();
+
+ if (millis() - last > 2000) {
+#if defined(TARGET_RP2040)
+ Serial.printf("FreeHeap: %d", rp2040.getFreeHeap());
+#else
+ Serial.printf("FreeHeap: %" PRIu32, ESP.getFreeHeap());
+#endif
+ last = millis();
+ }
+}
\ No newline at end of file
diff --git a/examples/simple_server/simple_server.ino b/examples/simple_server/simple_server.ino
deleted file mode 100644
index bdbcf60dc..000000000
--- a/examples/simple_server/simple_server.ino
+++ /dev/null
@@ -1,74 +0,0 @@
-//
-// A simple server implementation showing how to:
-// * serve static messages
-// * read GET and POST parameters
-// * handle missing pages / 404s
-//
-
-#include
-#ifdef ESP32
-#include
-#include
-#elif defined(ESP8266)
-#include
-#include
-#endif
-#include
-
-AsyncWebServer server(80);
-
-const char* ssid = "YOUR_SSID";
-const char* password = "YOUR_PASSWORD";
-
-const char* PARAM_MESSAGE = "message";
-
-void notFound(AsyncWebServerRequest *request) {
- request->send(404, "text/plain", "Not found");
-}
-
-void setup() {
-
- Serial.begin(115200);
- WiFi.mode(WIFI_STA);
- WiFi.begin(ssid, password);
- if (WiFi.waitForConnectResult() != WL_CONNECTED) {
- Serial.printf("WiFi Failed!\n");
- return;
- }
-
- Serial.print("IP Address: ");
- Serial.println(WiFi.localIP());
-
- server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
- request->send(200, "text/plain", "Hello, world");
- });
-
- // Send a GET request to /get?message=
- server.on("/get", HTTP_GET, [] (AsyncWebServerRequest *request) {
- String message;
- if (request->hasParam(PARAM_MESSAGE)) {
- message = request->getParam(PARAM_MESSAGE)->value();
- } else {
- message = "No message sent";
- }
- request->send(200, "text/plain", "Hello, GET: " + message);
- });
-
- // Send a POST request to /post with a form field message set to
- server.on("/post", HTTP_POST, [](AsyncWebServerRequest *request){
- String message;
- if (request->hasParam(PARAM_MESSAGE, true)) {
- message = request->getParam(PARAM_MESSAGE, true)->value();
- } else {
- message = "No message sent";
- }
- request->send(200, "text/plain", "Hello, POST: " + message);
- });
-
- server.onNotFound(notFound);
-
- server.begin();
-}
-
-void loop() {
-}
\ No newline at end of file
diff --git a/keywords.txt b/keywords.txt
deleted file mode 100755
index c391e6c43..000000000
--- a/keywords.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-JsonArray KEYWORD1
-add KEYWORD2
-createArray KEYWORD3
diff --git a/library.json b/library.json
index 57d1e7dd4..191a6e6a6 100644
--- a/library.json
+++ b/library.json
@@ -1,29 +1,57 @@
{
- "name":"ESP Async WebServer",
- "description":"Asynchronous HTTP and WebSocket Server Library for ESP8266 and ESP32",
- "keywords":"http,async,websocket,webserver",
+ "name": "ESPAsyncWebServer",
+ "version": "3.6.0",
+ "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",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/ESP32Async/ESPAsyncWebServer.git"
+ },
"authors":
{
- "name": "Hristo Gochkov",
+ "name": "ESP32Async",
"maintainer": true
},
- "repository":
- {
- "type": "git",
- "url": "https://github.com/me-no-dev/ESPAsyncWebServer.git"
- },
- "version": "1.2.2",
"license": "LGPL-3.0",
"frameworks": "arduino",
- "platforms": ["espressif8266", "espressif32"],
+ "platforms": [
+ "espressif32",
+ "espressif8266",
+ "raspberrypi"
+ ],
"dependencies": [
{
+ "owner": "ESP32Async",
+ "name": "AsyncTCP",
+ "version": "^3.3.2",
+ "platforms": "espressif32"
+ },
+ {
+ "owner": "ESP32Async",
"name": "ESPAsyncTCP",
+ "version": "^2.0.0",
"platforms": "espressif8266"
},
{
- "name": "AsyncTCP",
- "platforms": "espressif32"
+ "name": "Hash",
+ "platforms": "espressif8266"
+ },
+ {
+ "owner": "khoih-prog",
+ "name": "AsyncTCP_RP2040W",
+ "version": "^1.2.0",
+ "platforms": "raspberrypi"
}
- ]
+ ],
+ "export": {
+ "include": [
+ "examples",
+ "src",
+ "library.json",
+ "library.properties",
+ "LICENSE",
+ "README.md"
+ ]
+ }
}
diff --git a/library.properties b/library.properties
index 34b89fe86..d10f24296 100644
--- a/library.properties
+++ b/library.properties
@@ -1,9 +1,11 @@
name=ESP Async WebServer
-version=1.2.2
-author=Me-No-Dev
-maintainer=Me-No-Dev
-sentence=Async Web Server for ESP8266 and ESP31B
-paragraph=Async Web Server for ESP8266 and ESP31B
+includes=ESPAsyncWebServer.h
+version=3.6.0
+author=ESP32Async
+maintainer=ESP32Async
+sentence=Asynchronous HTTP and WebSocket Server Library for ESP32, ESP8266 and RP2040
+paragraph=Supports: WebSocket, SSE, Authentication, Arduino Json 7, File Upload, Static File serving, URL Rewrite, URL Redirect, etc
category=Other
-url=https://github.com/me-no-dev/ESPAsyncWebServer
+url=https://github.com/ESP32Async/ESPAsyncWebServer
architectures=*
+license=LGPL-3.0
\ No newline at end of file
diff --git a/partitions-4MB.csv b/partitions-4MB.csv
new file mode 100644
index 000000000..75efc35ce
--- /dev/null
+++ b/partitions-4MB.csv
@@ -0,0 +1,7 @@
+# Name ,Type ,SubType ,Offset ,Size ,Flags
+nvs ,data ,nvs ,36K ,20K ,
+otadata ,data ,ota ,56K ,8K ,
+app0 ,app ,ota_0 ,64K ,1856K ,
+app1 ,app ,ota_1 ,1920K ,1856K ,
+spiffs ,data ,spiffs ,3776K ,256K ,
+coredump ,data ,coredump ,4032K ,64K ,
diff --git a/platformio.ini b/platformio.ini
new file mode 100644
index 000000000..a731a50ed
--- /dev/null
+++ b/platformio.ini
@@ -0,0 +1,129 @@
+[platformio]
+default_envs = arduino-2, arduino-3, arduino-311, esp8266, raspberrypi
+lib_dir = .
+; src_dir = examples/CaptivePortal
+src_dir = examples/SimpleServer
+; src_dir = examples/StreamFiles
+; src_dir = examples/Filters
+; src_dir = examples/Issue85
+; src_dir = examples/Issue162
+
+[env]
+framework = arduino
+build_flags =
+ -Og
+ -Wall -Wextra
+ -Wno-unused-parameter
+ ; -D CONFIG_ARDUHAL_LOG_COLORS
+ -D CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_VERBOSE
+ -D CONFIG_ASYNC_TCP_MAX_ACK_TIME=5000
+ -D CONFIG_ASYNC_TCP_PRIORITY=10
+ -D CONFIG_ASYNC_TCP_QUEUE_SIZE=64
+ -D CONFIG_ASYNC_TCP_RUNNING_CORE=1
+ -D CONFIG_ASYNC_TCP_STACK_SIZE=4096
+upload_protocol = esptool
+monitor_speed = 115200
+monitor_filters = esp32_exception_decoder, log2file
+; monitor_filters = esp8266_exception_decoder, log2file
+lib_compat_mode = strict
+lib_ldf_mode = chain
+lib_deps =
+ ; bblanchon/ArduinoJson @ 5.13.4
+ ; bblanchon/ArduinoJson @ 6.21.5
+ bblanchon/ArduinoJson @ 7.3.0
+ ESP32Async/AsyncTCP @ 3.3.2
+board = esp32dev
+board_build.partitions = partitions-4MB.csv
+board_build.filesystem = littlefs
+
+[env:arduino-2]
+platform = espressif32@6.9.0
+
+[env:arduino-3]
+platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip
+; board = esp32-s3-devkitc-1
+; board = esp32-c6-devkitc-1
+
+[env:arduino-3-no-json]
+platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip
+; board = esp32-s3-devkitc-1
+; board = esp32-c6-devkitc-1
+lib_deps =
+ ESP32Async/AsyncTCP @ 3.3.2
+
+[env:arduino-311]
+platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.11/platform-espressif32.zip
+; board = esp32-s3-devkitc-1
+; board = esp32-c6-devkitc-1
+; board = esp32-h2-devkitm-1
+
+[env:perf-test-AsyncTCP]
+platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.11/platform-espressif32.zip
+build_flags = ${env.build_flags}
+ -D PERF_TEST=1
+
+[env:perf-test-AsyncTCPSock]
+platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.11/platform-espressif32.zip
+lib_deps =
+ https://github.com/ESP32Async/AsyncTCPSock/archive/refs/tags/v1.0.3-dev.zip
+build_flags = ${env.build_flags}
+ -D PERF_TEST=1
+
+[env:esp8266]
+platform = espressif8266
+; board = huzzah
+board = d1_mini
+lib_deps =
+ bblanchon/ArduinoJson @ 7.3.0
+ ESP32Async/ESPAsyncTCP @ 2.0.0
+
+[env:raspberrypi]
+platform = https://github.com/maxgerhardt/platform-raspberrypi.git
+board = rpipicow
+board_build.core = earlephilhower
+lib_deps =
+ bblanchon/ArduinoJson @ 7.3.0
+ khoih-prog/AsyncTCP_RP2040W @ 1.2.0
+lib_ignore =
+ lwIP_ESPHost
+build_flags = ${env.build_flags}
+ -Wno-missing-field-initializers
+
+; CI
+
+[env:ci-arduino-2]
+platform = espressif32@6.9.0
+board = ${sysenv.PIO_BOARD}
+
+[env:ci-arduino-3]
+platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip
+board = ${sysenv.PIO_BOARD}
+
+[env:ci-arduino-3-no-json]
+platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip
+board = ${sysenv.PIO_BOARD}
+lib_deps =
+ ESP32Async/AsyncTCP @ 3.3.2
+
+[env:ci-arduino-311]
+platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.11/platform-espressif32.zip
+board = ${sysenv.PIO_BOARD}
+
+[env:ci-esp8266]
+platform = espressif8266
+board = ${sysenv.PIO_BOARD}
+lib_deps =
+ bblanchon/ArduinoJson @ 7.3.0
+ ESP32Async/ESPAsyncTCP @ 2.0.0
+
+[env:ci-raspberrypi]
+platform = https://github.com/maxgerhardt/platform-raspberrypi.git
+board = ${sysenv.PIO_BOARD}
+board_build.core = earlephilhower
+lib_deps =
+ bblanchon/ArduinoJson @ 7.3.0
+ khoih-prog/AsyncTCP_RP2040W @ 1.2.0
+lib_ignore =
+ lwIP_ESPHost
+build_flags = ${env.build_flags}
+ -Wno-missing-field-initializers
diff --git a/src/AsyncEventSource.cpp b/src/AsyncEventSource.cpp
index 0707d4a3a..874faa049 100644
--- a/src/AsyncEventSource.cpp
+++ b/src/AsyncEventSource.cpp
@@ -18,329 +18,450 @@
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "Arduino.h"
+#if defined(ESP32)
+ #include
+#endif
#include "AsyncEventSource.h"
-static String generateEventMessage(const char *message, const char *event, uint32_t id, uint32_t reconnect){
- String ev = "";
+#define ASYNC_SSE_NEW_LINE_CHAR (char)0xa
- if(reconnect){
- ev += "retry: ";
- ev += String(reconnect);
- ev += "\r\n";
+using namespace asyncsrv;
+
+static String generateEventMessage(const char* message, const char* event, uint32_t id, uint32_t reconnect) {
+ String str;
+ size_t len{0};
+ if (message)
+ len += strlen(message);
+
+ if (event)
+ len += strlen(event);
+
+ len += 42; // give it some overhead
+
+ str.reserve(len);
+
+ if (reconnect) {
+ str += T_retry_;
+ str += reconnect;
+ str += ASYNC_SSE_NEW_LINE_CHAR; // '\n'
}
- if(id){
- ev += "id: ";
- ev += String(id);
- ev += "\r\n";
+ if (id) {
+ str += T_id__;
+ str += id;
+ str += ASYNC_SSE_NEW_LINE_CHAR; // '\n'
}
- if(event != NULL){
- ev += "event: ";
- ev += String(event);
- ev += "\r\n";
+ if (event != NULL) {
+ str += T_event_;
+ str += event;
+ str += ASYNC_SSE_NEW_LINE_CHAR; // '\n'
}
- if(message != NULL){
- size_t messageLen = strlen(message);
- char * lineStart = (char *)message;
- char * lineEnd;
- do {
- char * nextN = strchr(lineStart, '\n');
- char * nextR = strchr(lineStart, '\r');
- if(nextN == NULL && nextR == NULL){
- size_t llen = ((char *)message + messageLen) - lineStart;
- char * ldata = (char *)malloc(llen+1);
- if(ldata != NULL){
- memcpy(ldata, lineStart, llen);
- ldata[llen] = 0;
- ev += "data: ";
- ev += ldata;
- ev += "\r\n\r\n";
- free(ldata);
- }
- lineStart = (char *)message + messageLen;
+ if (!message)
+ return str;
+
+ size_t messageLen = strlen(message);
+ char* lineStart = (char*)message;
+ char* lineEnd;
+ do {
+ 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_;
+ str += message;
+ str += T_nn;
+ return str;
+ }
+
+ // a message is a multi-line string
+ char* nextLine = NULL;
+ if (nextN != NULL && nextR != NULL) { // windows line-ending \r\n
+ if (nextR + 1 == nextN) {
+ // normal \r\n sequense
+ lineEnd = nextR;
+ nextLine = nextN + 1;
} else {
- char * nextLine = NULL;
- if(nextN != NULL && nextR != NULL){
- if(nextR < nextN){
- lineEnd = nextR;
- if(nextN == (nextR + 1))
- nextLine = nextN + 1;
- else
- nextLine = nextR + 1;
- } else {
- lineEnd = nextN;
- if(nextR == (nextN + 1))
- nextLine = nextR + 1;
- else
- nextLine = nextN + 1;
- }
- } else if(nextN != NULL){
- lineEnd = nextN;
- nextLine = nextN + 1;
- } else {
- lineEnd = nextR;
- nextLine = nextR + 1;
- }
-
- size_t llen = lineEnd - lineStart;
- char * ldata = (char *)malloc(llen+1);
- if(ldata != NULL){
- memcpy(ldata, lineStart, llen);
- ldata[llen] = 0;
- ev += "data: ";
- ev += ldata;
- ev += "\r\n";
- free(ldata);
- }
- lineStart = nextLine;
- if(lineStart == ((char *)message + messageLen))
- ev += "\r\n";
+ // some abnormal \n \r mixed sequence
+ lineEnd = std::min(nextR, nextN);
+ nextLine = lineEnd + 1;
}
- } while(lineStart < ((char *)message + messageLen));
- }
+ } else if (nextN != NULL) { // Unix/Mac OS X LF
+ lineEnd = nextN;
+ nextLine = nextN + 1;
+ } else { // some ancient garbage
+ lineEnd = nextR;
+ nextLine = nextR + 1;
+ }
- return ev;
-}
+ str += T_data_;
+ str.concat(lineStart, lineEnd - lineStart);
+ str += ASYNC_SSE_NEW_LINE_CHAR; // \n
-// Message
+ lineStart = nextLine;
+ } while (lineStart < ((char*)message + messageLen));
-AsyncEventSourceMessage::AsyncEventSourceMessage(const char * data, size_t len)
-: _data(nullptr), _len(len), _sent(0), _acked(0)
-{
- _data = (uint8_t*)malloc(_len+1);
- if(_data == nullptr){
- _len = 0;
- } else {
- memcpy(_data, data, len);
- _data[_len] = 0;
- }
-}
+ // append another \n to terminate message
+ str += ASYNC_SSE_NEW_LINE_CHAR; // '\n'
-AsyncEventSourceMessage::~AsyncEventSourceMessage() {
- if(_data != NULL)
- free(_data);
+ return str;
}
-size_t AsyncEventSourceMessage::ack(size_t len, uint32_t time) {
+// Message
+
+size_t AsyncEventSourceMessage::ack(size_t len, __attribute__((unused)) uint32_t time) {
// If the whole message is now acked...
- if(_acked + len > _len){
- // Return the number of extra bytes acked (they will be carried on to the next message)
- const size_t extra = _acked + len - _len;
- _acked = _len;
- return extra;
+ if (_acked + len > _data->length()) {
+ // Return the number of extra bytes acked (they will be carried on to the next message)
+ const size_t extra = _acked + len - _data->length();
+ _acked = _data->length();
+ return extra;
}
// Return that no extra bytes left.
_acked += len;
return 0;
}
-size_t AsyncEventSourceMessage::send(AsyncClient *client) {
- const size_t len = _len - _sent;
- if(client->space() < len){
+size_t AsyncEventSourceMessage::write(AsyncClient* client) {
+ if (!client)
+ return 0;
+
+ if (_sent >= _data->length() || !client->canSend()) {
return 0;
}
- size_t sent = client->add((const char *)_data, len);
- if(client->canSend())
- client->send();
- _sent += sent;
- return sent;
+
+ size_t len = std::min(_data->length() - _sent, client->space());
+ /*
+ add() would call lwip's tcp_write() under the AsyncTCP hood with apiflags argument.
+ By default apiflags=ASYNC_WRITE_FLAG_COPY
+ we could have used 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
+ 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
+
+ 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
+ _sent += written;
+ return written;
+}
+
+size_t AsyncEventSourceMessage::send(AsyncClient* client) {
+ size_t sent = write(client);
+ return sent && client->send() ? sent : 0;
}
// Client
-AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server)
-: _messageQueue(LinkedList([](AsyncEventSourceMessage *m){ delete m; }))
-{
- _client = request->client();
- _server = server;
- _lastId = 0;
- if(request->hasHeader("Last-Event-ID"))
- _lastId = atoi(request->getHeader("Last-Event-ID")->value().c_str());
-
+AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest* request, AsyncEventSource* server)
+ : _client(request->client()), _server(server) {
+
+ 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){ ((AsyncEventSourceClient*)(r))->_onAck(len, time); }, this);
- _client->onPoll([](void *r, AsyncClient* c){ ((AsyncEventSourceClient*)(r))->_onPoll(); }, this);
+ _client->onAck([](void* r, AsyncClient* c, size_t len, uint32_t time) { (void)c; static_cast(r)->_onAck(len, time); }, this);
+ _client->onPoll([](void* r, AsyncClient* c) { (void)c; static_cast(r)->_onPoll(); }, this);
_client->onData(NULL, NULL);
- _client->onTimeout([this](void *r, AsyncClient* c __attribute__((unused)), uint32_t time){ ((AsyncEventSourceClient*)(r))->_onTimeout(time); }, this);
- _client->onDisconnect([this](void *r, AsyncClient* c){ ((AsyncEventSourceClient*)(r))->_onDisconnect(); delete c; }, this);
+ _client->onTimeout([this](void* r, AsyncClient* c __attribute__((unused)), uint32_t time) { static_cast(r)->_onTimeout(time); }, this);
+ _client->onDisconnect([this](void* r, AsyncClient* c) { static_cast(r)->_onDisconnect(); delete c; }, this);
_server->_addClient(this);
delete request;
+
+ _client->setNoDelay(true);
}
-AsyncEventSourceClient::~AsyncEventSourceClient(){
- _messageQueue.free();
+AsyncEventSourceClient::~AsyncEventSourceClient() {
+#ifdef ESP32
+ std::lock_guard lock(_lockmq);
+#endif
+ _messageQueue.clear();
close();
}
-void AsyncEventSourceClient::_queueMessage(AsyncEventSourceMessage *dataMessage){
- if(dataMessage == NULL)
- return;
- if(!connected()){
- delete dataMessage;
- return;
+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());
+#elif defined(ESP32)
+ log_e("Event message queue overflow: discard message");
+#endif
+ return false;
}
- _messageQueue.add(dataMessage);
+#ifdef ESP32
+ // length() is not thread-safe, thus acquiring the lock before this call..
+ std::lock_guard lock(_lockmq);
+#endif
- _runQueue();
-}
+ _messageQueue.emplace_back(message, len);
-void AsyncEventSourceClient::_onAck(size_t len, uint32_t time){
- while(len && !_messageQueue.isEmpty()){
- len = _messageQueue.front()->ack(len, time);
- if(_messageQueue.front()->finished())
- _messageQueue.remove(_messageQueue.front());
+ /*
+ throttle queue run
+ if Q is filled for >25% then network/CPU is congested, since there is no zero-copy mode for socket buff
+ forcing Q run will only eat more heap ram and blow the buffer, let's just keep data in our own queue
+ the queue will be processed at least on each onAck()/onPoll() call from AsyncTCP
+ */
+ if (_messageQueue.size() < SSE_MAX_QUEUED_MESSAGES >> 2 && _client->canSend()) {
+ _runQueue();
}
- _runQueue();
+ return true;
}
-void AsyncEventSourceClient::_onPoll(){
- if(!_messageQueue.isEmpty()){
+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());
+#elif defined(ESP32)
+ log_e("Event message queue overflow: discard message");
+#endif
+ return false;
+ }
+
+#ifdef ESP32
+ // length() is not thread-safe, thus acquiring the lock before this call..
+ std::lock_guard lock(_lockmq);
+#endif
+
+ _messageQueue.emplace_back(std::move(msg));
+
+ /*
+ throttle queue run
+ if Q is filled for >25% then network/CPU is congested, since there is no zero-copy mode for socket buff
+ forcing Q run will only eat more heap ram and blow the buffer, let's just keep data in our own queue
+ the queue will be processed at least on each onAck()/onPoll() call from AsyncTCP
+ */
+ if (_messageQueue.size() < SSE_MAX_QUEUED_MESSAGES >> 2 && _client->canSend()) {
_runQueue();
}
+ return true;
+}
+
+void AsyncEventSourceClient::_onAck(size_t len __attribute__((unused)), uint32_t time __attribute__((unused))) {
+#ifdef ESP32
+ // Same here, acquiring the lock early
+ std::lock_guard lock(_lockmq);
+#endif
+
+ // adjust in-flight len
+ if (len < _inflight)
+ _inflight -= len;
+ else
+ _inflight = 0;
+
+ // acknowledge as much messages's data as we got confirmed len from a AsyncTCP
+ while (len && _messageQueue.size()) {
+ len = _messageQueue.front().ack(len);
+ if (_messageQueue.front().finished()) {
+ // now we could release full ack'ed messages, we were keeping it unless send confirmed from AsyncTCP
+ _messageQueue.pop_front();
+ }
+ }
+
+ // try to send another batch of data
+ if (_messageQueue.size())
+ _runQueue();
}
+void AsyncEventSourceClient::_onPoll() {
+ if (_messageQueue.size()) {
+#ifdef ESP32
+ // Same here, acquiring the lock early
+ std::lock_guard lock(_lockmq);
+#endif
+ _runQueue();
+ }
+}
-void AsyncEventSourceClient::_onTimeout(uint32_t time __attribute__((unused))){
- _client->close(true);
+void AsyncEventSourceClient::_onTimeout(uint32_t time __attribute__((unused))) {
+ if (_client)
+ _client->close(true);
}
-void AsyncEventSourceClient::_onDisconnect(){
- _client = NULL;
+void AsyncEventSourceClient::_onDisconnect() {
+ if (!_client)
+ return;
+ _client = nullptr;
_server->_handleDisconnect(this);
}
-void AsyncEventSourceClient::close(){
- if(_client != NULL)
+void AsyncEventSourceClient::close() {
+ if (_client)
_client->close();
}
-void AsyncEventSourceClient::write(const char * message, size_t len){
- _queueMessage(new AsyncEventSourceMessage(message, len));
+bool AsyncEventSourceClient::send(const char* message, const char* event, uint32_t id, uint32_t reconnect) {
+ if (!connected())
+ return false;
+ return _queueMessage(std::make_shared(generateEventMessage(message, event, id, reconnect)));
}
-void AsyncEventSourceClient::send(const char *message, const char *event, uint32_t id, uint32_t reconnect){
- String ev = generateEventMessage(message, event, id, reconnect);
- _queueMessage(new AsyncEventSourceMessage(ev.c_str(), ev.length()));
-}
+void AsyncEventSourceClient::_runQueue() {
+ if (!_client)
+ return;
-void AsyncEventSourceClient::_runQueue(){
- while(!_messageQueue.isEmpty() && _messageQueue.front()->finished()){
- _messageQueue.remove(_messageQueue.front());
+ // 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;
+ for (auto i = _messageQueue.begin(); i != _messageQueue.end(); ++i) {
+ if (!i->sent()) {
+ const size_t bytes_written = i->write(_client);
+ total_bytes_written += bytes_written;
+ _inflight += bytes_written;
+ if (bytes_written == 0 || _inflight > _max_inflight) {
+ // Serial.print("_");
+ break;
+ }
+ }
}
- for(auto i = _messageQueue.begin(); i != _messageQueue.end(); ++i)
- {
- if(!(*i)->sent())
- (*i)->send(_client);
- }
+ // flush socket
+ if (total_bytes_written)
+ _client->send();
}
-
-// Handler
-
-AsyncEventSource::AsyncEventSource(const String& url)
- : _url(url)
- , _clients(LinkedList([](AsyncEventSourceClient *c){ delete c; }))
- , _connectcb(NULL)
-{}
-
-AsyncEventSource::~AsyncEventSource(){
- close();
+void AsyncEventSourceClient::set_max_inflight_bytes(size_t value) {
+ if (value >= SSE_MIN_INFLIGH && value <= SSE_MAX_INFLIGH)
+ _max_inflight = value;
}
-void AsyncEventSource::onConnect(ArEventHandlerFunction cb){
- _connectcb = cb;
+/* AsyncEventSource */
+
+void AsyncEventSource::authorizeConnect(ArAuthorizeConnectHandler cb) {
+ AsyncAuthorizationMiddleware* m = new AsyncAuthorizationMiddleware(401, cb);
+ m->_freeOnRemoval = true;
+ addMiddleware(m);
}
-void AsyncEventSource::_addClient(AsyncEventSourceClient * client){
- /*char * temp = (char *)malloc(2054);
- if(temp != NULL){
- memset(temp+1,' ',2048);
- temp[0] = ':';
- temp[2049] = '\r';
- temp[2050] = '\n';
- temp[2051] = '\r';
- temp[2052] = '\n';
- temp[2053] = 0;
- client->write((const char *)temp, 2053);
- free(temp);
- }*/
-
- _clients.add(client);
- if(_connectcb)
+void AsyncEventSource::_addClient(AsyncEventSourceClient* client) {
+ if (!client)
+ return;
+#ifdef ESP32
+ std::lock_guard lock(_client_queue_lock);
+#endif
+ _clients.emplace_back(client);
+ if (_connectcb)
_connectcb(client);
+
+ _adjust_inflight_window();
}
-void AsyncEventSource::_handleDisconnect(AsyncEventSourceClient * client){
- _clients.remove(client);
+void AsyncEventSource::_handleDisconnect(AsyncEventSourceClient* client) {
+ if (_disconnectcb)
+ _disconnectcb(client);
+#ifdef ESP32
+ std::lock_guard lock(_client_queue_lock);
+#endif
+ for (auto i = _clients.begin(); i != _clients.end(); ++i) {
+ if (i->get() == client)
+ _clients.erase(i);
+ }
+ _adjust_inflight_window();
}
-void AsyncEventSource::close(){
- for(const auto &c: _clients){
- if(c->connected())
+void AsyncEventSource::close() {
+ // While the whole loop is not done, the linked list is locked and so the
+ // iterator should remain valid even when AsyncEventSource::_handleDisconnect()
+ // is called very early
+#ifdef ESP32
+ std::lock_guard lock(_client_queue_lock);
+#endif
+ for (const auto& c : _clients) {
+ if (c->connected())
c->close();
}
}
-void AsyncEventSource::send(const char *message, const char *event, uint32_t id, uint32_t reconnect){
- if(_clients.isEmpty())
- return;
+// pmb fix
+size_t AsyncEventSource::avgPacketsWaiting() const {
+ size_t aql = 0;
+ uint32_t nConnectedClients = 0;
+#ifdef ESP32
+ std::lock_guard lock(_client_queue_lock);
+#endif
+ if (!_clients.size())
+ return 0;
- String ev = generateEventMessage(message, event, id, reconnect);
- for(const auto &c: _clients){
- if(c->connected()) {
- c->write(ev.c_str(), ev.length());
+ for (const auto& c : _clients) {
+ if (c->connected()) {
+ aql += c->packetsWaiting();
+ ++nConnectedClients;
}
}
+ return ((aql) + (nConnectedClients / 2)) / (nConnectedClients); // round up
+}
+
+AsyncEventSource::SendStatus AsyncEventSource::send(
+ const char* message, const char* event, uint32_t id, uint32_t reconnect) {
+ AsyncEvent_SharedData_t shared_msg = std::make_shared(generateEventMessage(message, event, id, reconnect));
+#ifdef ESP32
+ std::lock_guard lock(_client_queue_lock);
+#endif
+ size_t hits = 0;
+ size_t miss = 0;
+ for (const auto& c : _clients) {
+ if (c->write(shared_msg))
+ ++hits;
+ else
+ ++miss;
+ }
+ return hits == 0 ? DISCARDED : (miss == 0 ? ENQUEUED : PARTIALLY_ENQUEUED);
}
size_t AsyncEventSource::count() const {
- return _clients.count_if([](AsyncEventSourceClient *c){
- return c->connected();
- });
+#ifdef ESP32
+ std::lock_guard lock(_client_queue_lock);
+#endif
+ size_t n_clients{0};
+ for (const auto& i : _clients)
+ if (i->connected())
+ ++n_clients;
+
+ return n_clients;
}
-bool AsyncEventSource::canHandle(AsyncWebServerRequest *request){
- if(request->method() != HTTP_GET || !request->url().equals(_url)) {
- return false;
- }
- request->addInterestingHeader("Last-Event-ID");
- return true;
+bool AsyncEventSource::canHandle(AsyncWebServerRequest* request) const {
+ return request->isSSE() && request->url().equals(_url);
}
-void AsyncEventSource::handleRequest(AsyncWebServerRequest *request){
- if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str()))
- return request->requestAuthentication();
+void AsyncEventSource::handleRequest(AsyncWebServerRequest* request) {
request->send(new AsyncEventSourceResponse(this));
}
-// Response
+void AsyncEventSource::_adjust_inflight_window() {
+ if (_clients.size()) {
+ size_t inflight = SSE_MAX_INFLIGH / _clients.size();
+ for (const auto& c : _clients)
+ c->set_max_inflight_bytes(inflight);
+ // Serial.printf("adjusted inflight to: %u\n", inflight);
+ }
+}
-AsyncEventSourceResponse::AsyncEventSourceResponse(AsyncEventSource *server){
+/* Response */
+
+AsyncEventSourceResponse::AsyncEventSourceResponse(AsyncEventSource* server) {
_server = server;
_code = 200;
- _contentType = "text/event-stream";
+ _contentType = T_text_event_stream;
_sendContentLength = false;
- addHeader("Cache-Control", "no-cache");
- addHeader("Connection","keep-alive");
+ addHeader(T_Cache_Control, T_no_cache);
+ addHeader(T_Connection, T_keep_alive);
}
-void AsyncEventSourceResponse::_respond(AsyncWebServerRequest *request){
- String out = _assembleHead(request->version());
+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))){
- if(len){
+size_t AsyncEventSourceResponse::_ack(AsyncWebServerRequest* request, size_t len, uint32_t time __attribute__((unused))) {
+ if (len) {
new AsyncEventSourceClient(request, _server);
}
return 0;
}
-
diff --git a/src/AsyncEventSource.h b/src/AsyncEventSource.h
index 1b4d604dc..0796faedc 100644
--- a/src/AsyncEventSource.h
+++ b/src/AsyncEventSource.h
@@ -21,94 +21,272 @@
#define ASYNCEVENTSOURCE_H_
#include
+
#ifdef ESP32
-#include
-#else
-#include
+ #include
+ #include
+ #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
+ #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
+ #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
+#ifdef ESP8266
+ #include
+ #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;
-typedef std::function ArEventHandlerFunction;
+using ArEventHandlerFunction = std::function;
+using ArAuthorizeConnectHandler = ArAuthorizeFunction;
+// shared message object container
+using AsyncEvent_SharedData_t = std::shared_ptr;
+/**
+ * @brief Async Event Message container with shared message content data
+ *
+ */
class AsyncEventSourceMessage {
+
private:
- uint8_t * _data;
- size_t _len;
- size_t _sent;
- //size_t _ack;
- size_t _acked;
+ const AsyncEvent_SharedData_t _data;
+ size_t _sent{0}; // num of bytes already sent
+ size_t _acked{0}; // num of bytes acked
+
public:
- AsyncEventSourceMessage(const char * data, size_t len);
- ~AsyncEventSourceMessage();
- size_t ack(size_t len, uint32_t time __attribute__((unused)));
- size_t send(AsyncClient *client);
- bool finished(){ return _acked == _len; }
- bool sent() { return _sent == _len; }
+ AsyncEventSourceMessage(AsyncEvent_SharedData_t data) : _data(data) {};
+#ifdef ESP32
+ AsyncEventSourceMessage(const char* data, size_t len) : _data(std::make_shared(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); };
+#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 time
+ * @return size_t number of extra bytes carried over
+ */
+ 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);
+
+ /**
+ * @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);
+
+ // 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(); }
};
+/**
+ * @brief class holds a sse messages queue for a particular client's connection
+ *
+ */
class AsyncEventSourceClient {
private:
- AsyncClient *_client;
- AsyncEventSource *_server;
- uint32_t _lastId;
- LinkedList _messageQueue;
- void _queueMessage(AsyncEventSourceMessage *dataMessage);
+ 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 _messageQueue;
+#ifdef ESP32
+ mutable std::mutex _lockmq;
+#endif
+ bool _queueMessage(const char* message, size_t len);
+ bool _queueMessage(AsyncEvent_SharedData_t&& msg);
void _runQueue();
public:
-
- AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server);
+ AsyncEventSourceClient(AsyncWebServerRequest* request, AsyncEventSource* server);
~AsyncEventSourceClient();
- AsyncClient* client(){ return _client; }
+ /**
+ * @brief Send an SSE message to client
+ * it will craft an SSE message and place it to client's message queue
+ *
+ * @param message body string, could be single or multi-line string sepprated by \n, \r, \r\n
+ * @param event body string, a sinle line string
+ * @param id sequence id
+ * @param reconnect client's reconnect timeout
+ * @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); }
+
+ /**
+ * @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
+ *
+ * @param message data
+ * @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)); };
+
+ [[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();
- void write(const char * message, size_t len);
- void send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0);
- bool connected() const { return (_client != NULL) && _client->connected(); }
+
+ // 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(); };
+
+ /**
+ * @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);
- //system callbacks (do not call)
+ /**
+ * @brief Get current max inflight bytes value
+ *
+ * @return size_t
+ */
+ 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 _onPoll();
void _onTimeout(uint32_t time);
void _onDisconnect();
};
-class AsyncEventSource: public AsyncWebHandler {
+/**
+ * @brief a class that maintains all connected HTTP clients subscribed to SSE delivery
+ * dispatches supplied messages to the client's queues
+ *
+ */
+class AsyncEventSource : public AsyncWebHandler {
private:
String _url;
- LinkedList _clients;
- ArEventHandlerFunction _connectcb;
+ std::list> _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;
+#endif
+ 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();
+
public:
- AsyncEventSource(const String& url);
- ~AsyncEventSource();
+ typedef enum {
+ DISCARDED = 0,
+ ENQUEUED = 1,
+ PARTIALLY_ENQUEUED = 2,
+ } SendStatus;
- const char * url() const { return _url.c_str(); }
+ 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();
- void onConnect(ArEventHandlerFunction cb);
- void send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0);
- size_t count() const; //number clinets connected
-
- //system callbacks (do not call)
- void _addClient(AsyncEventSourceClient * client);
- void _handleDisconnect(AsyncEventSourceClient * client);
- virtual bool canHandle(AsyncWebServerRequest *request) override final;
- virtual void handleRequest(AsyncWebServerRequest *request) override final;
+
+ /**
+ * @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; }
+
+ /**
+ * @brief Send an SSE message to client
+ * it will craft an SSE message and place it to all connected client's message queues
+ *
+ * @param message body string, could be single or multi-line string sepprated by \n, \r, \r\n
+ * @param event body string, a sinle line string
+ * @param id sequence id
+ * @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); }
+
+ // 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 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;
};
-class AsyncEventSourceResponse: public AsyncWebServerResponse {
+class AsyncEventSourceResponse : public AsyncWebServerResponse {
private:
- String _content;
- AsyncEventSource *_server;
+ AsyncEventSource* _server;
+
public:
- AsyncEventSourceResponse(AsyncEventSource *server);
- void _respond(AsyncWebServerRequest *request);
- size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time);
+ 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_ */
diff --git a/src/AsyncJson.cpp b/src/AsyncJson.cpp
new file mode 100644
index 000000000..42956537d
--- /dev/null
+++ b/src/AsyncJson.cpp
@@ -0,0 +1,151 @@
+#include "AsyncJson.h"
+
+#if ASYNC_JSON_SUPPORT == 1
+
+ #if ARDUINOJSON_VERSION_MAJOR == 5
+AsyncJsonResponse::AsyncJsonResponse(bool isArray) : _isValid{false} {
+ _code = 200;
+ _contentType = asyncsrv::T_application_json;
+ if (isArray)
+ _root = _jsonBuffer.createArray();
+ else
+ _root = _jsonBuffer.createObject();
+}
+ #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)
+ _root = _jsonBuffer.createNestedArray();
+ else
+ _root = _jsonBuffer.createNestedObject();
+}
+ #else
+AsyncJsonResponse::AsyncJsonResponse(bool isArray) : _isValid{false} {
+ _code = 200;
+ _contentType = asyncsrv::T_application_json;
+ if (isArray)
+ _root = _jsonBuffer.add();
+ else
+ _root = _jsonBuffer.add();
+}
+ #endif
+
+size_t AsyncJsonResponse::setLength() {
+ #if ARDUINOJSON_VERSION_MAJOR == 5
+ _contentLength = _root.measureLength();
+ #else
+ _contentLength = measureJson(_root);
+ #endif
+ if (_contentLength) {
+ _isValid = true;
+ }
+ return _contentLength;
+}
+
+size_t AsyncJsonResponse::_fillBuffer(uint8_t* data, size_t len) {
+ ChunkPrint dest(data, _sentLength, len);
+ #if ARDUINOJSON_VERSION_MAJOR == 5
+ _root.printTo(dest);
+ #else
+ serializeJson(_root, dest);
+ #endif
+ return len;
+}
+
+ #if ARDUINOJSON_VERSION_MAJOR == 6
+PrettyAsyncJsonResponse::PrettyAsyncJsonResponse(bool isArray, size_t maxJsonBufferSize) : AsyncJsonResponse{isArray, maxJsonBufferSize} {}
+ #else
+PrettyAsyncJsonResponse::PrettyAsyncJsonResponse(bool isArray) : AsyncJsonResponse{isArray} {}
+ #endif
+
+size_t PrettyAsyncJsonResponse::setLength() {
+ #if ARDUINOJSON_VERSION_MAJOR == 5
+ _contentLength = _root.measurePrettyLength();
+ #else
+ _contentLength = measureJsonPretty(_root);
+ #endif
+ if (_contentLength) {
+ _isValid = true;
+ }
+ return _contentLength;
+}
+
+size_t PrettyAsyncJsonResponse::_fillBuffer(uint8_t* data, size_t len) {
+ ChunkPrint dest(data, _sentLength, len);
+ #if ARDUINOJSON_VERSION_MAJOR == 5
+ _root.prettyPrintTo(dest);
+ #else
+ serializeJsonPretty(_root, dest);
+ #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
+
+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 + "/")))
+ return false;
+
+ if (request->method() != HTTP_GET && !request->contentType().equalsIgnoreCase(asyncsrv::T_application_json))
+ return false;
+
+ return true;
+}
+
+void AsyncCallbackJsonWebHandler::handleRequest(AsyncWebServerRequest* request) {
+ if (_onRequest) {
+ if (request->method() == HTTP_GET) {
+ JsonVariant json;
+ _onRequest(request, json);
+ return;
+ } else if (request->_tempObject != NULL) {
+
+ #if ARDUINOJSON_VERSION_MAJOR == 5
+ DynamicJsonBuffer jsonBuffer;
+ JsonVariant json = jsonBuffer.parse((uint8_t*)(request->_tempObject));
+ if (json.success()) {
+ #elif ARDUINOJSON_VERSION_MAJOR == 6
+ DynamicJsonDocument jsonBuffer(this->maxJsonBufferSize);
+ DeserializationError error = deserializeJson(jsonBuffer, (uint8_t*)(request->_tempObject));
+ if (!error) {
+ JsonVariant json = jsonBuffer.as();
+ #else
+ JsonDocument jsonBuffer;
+ DeserializationError error = deserializeJson(jsonBuffer, (uint8_t*)(request->_tempObject));
+ if (!error) {
+ JsonVariant json = jsonBuffer.as();
+ #endif
+
+ _onRequest(request, json);
+ return;
+ }
+ }
+ request->send(_contentLength > _maxContentLength ? 413 : 400);
+ } else {
+ request->send(500);
+ }
+}
+
+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) {
+ memcpy((uint8_t*)(request->_tempObject) + index, data, len);
+ }
+ }
+}
+
+#endif // ASYNC_JSON_SUPPORT
diff --git a/src/AsyncJson.h b/src/AsyncJson.h
index 4a6d6ba69..a54935adb 100644
--- a/src/AsyncJson.h
+++ b/src/AsyncJson.h
@@ -26,217 +26,106 @@
AsyncCallbackJsonWebHandler* handler = new AsyncCallbackJsonWebHandler("/rest/endpoint");
handler->onRequest([](AsyncWebServerRequest *request, JsonVariant &json) {
- JsonObject& jsonObj = json.as();
+ JsonObject jsonObj = json.as();
// ...
});
server.addHandler(handler);
-
+
*/
#ifndef ASYNC_JSON_H_
#define ASYNC_JSON_H_
-#include
-#include
-#include
-#if ARDUINOJSON_VERSION_MAJOR == 5
- #define ARDUINOJSON_5_COMPATIBILITY
-#else
- #define DYNAMIC_JSON_DOCUMENT_SIZE 1024
-#endif
+#if __has_include("ArduinoJson.h")
+ #include
+ #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")
-constexpr const char* JSON_MIMETYPE = "application/json";
+#if ASYNC_JSON_SUPPORT == 1
+ #include
-/*
- * Json Response
- * */
-
-class ChunkPrint : public Print {
- 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)
- : _destination(destination), _to_skip(from), _to_write(len), _pos{0} {}
- virtual ~ChunkPrint(){}
- size_t write(uint8_t c){
- if (_to_skip > 0) {
- _to_skip--;
- return 1;
- } else if (_to_write > 0) {
- _to_write--;
- _destination[_pos++] = c;
- return 1;
- }
- return 0;
- }
- size_t write(const uint8_t *buffer, size_t size)
- {
- return this->Print::write(buffer, size);
- }
-};
+ #include "ChunkPrint.h"
-class AsyncJsonResponse: public AsyncAbstractResponse {
- protected:
+ #if ARDUINOJSON_VERSION_MAJOR == 6
+ #ifndef DYNAMIC_JSON_DOCUMENT_SIZE
+ #define DYNAMIC_JSON_DOCUMENT_SIZE 1024
+ #endif
+ #endif
-#ifdef ARDUINOJSON_5_COMPATIBILITY
+class AsyncJsonResponse : public AsyncAbstractResponse {
+ protected:
+ #if ARDUINOJSON_VERSION_MAJOR == 5
DynamicJsonBuffer _jsonBuffer;
-#else
+ #elif ARDUINOJSON_VERSION_MAJOR == 6
DynamicJsonDocument _jsonBuffer;
-#endif
+ #else
+ JsonDocument _jsonBuffer;
+ #endif
JsonVariant _root;
bool _isValid;
- public:
-
-#ifdef ARDUINOJSON_5_COMPATIBILITY
- AsyncJsonResponse(bool isArray=false): _isValid{false} {
- _code = 200;
- _contentType = JSON_MIMETYPE;
- if(isArray)
- _root = _jsonBuffer.createArray();
- else
- _root = _jsonBuffer.createObject();
- }
-#else
- AsyncJsonResponse(size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE, bool isArray=false) : _jsonBuffer(maxJsonBufferSize), _isValid{false} {
- _code = 200;
- _contentType = JSON_MIMETYPE;
- if(isArray)
- _root = _jsonBuffer.createNestedArray();
- else
- _root = _jsonBuffer.createNestedObject();
- }
-#endif
-
- ~AsyncJsonResponse() {}
- JsonVariant & getRoot() { return _root; }
+ 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() {
-
-#ifdef ARDUINOJSON_5_COMPATIBILITY
- _contentLength = _root.measureLength();
-#else
- _contentLength = measureJson(_root);
-#endif
-
- if (_contentLength) { _isValid = true; }
- return _contentLength;
- }
-
- size_t getSize() { return _jsonBuffer.size(); }
-
- size_t _fillBuffer(uint8_t *data, size_t len){
- ChunkPrint dest(data, _sentLength, len);
-
-#ifdef ARDUINOJSON_5_COMPATIBILITY
- _root.printTo( dest ) ;
-#else
- serializeJson(_root, dest);
-#endif
- return len;
- }
+ 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
};
-#ifdef ARDUINOJSON_5_COMPATIBILITY
-class PrettyAsyncJsonResponse: public AsyncJsonResponse {
-public:
- PrettyAsyncJsonResponse (bool isArray=false) : AsyncJsonResponse{isArray} {}
- size_t setLength () {
- _contentLength = _root.measurePrettyLength ();
- if (_contentLength) {_isValid = true;}
- return _contentLength;
- }
- size_t _fillBuffer (uint8_t *data, size_t len) {
- ChunkPrint dest (data, _sentLength, len);
- _root.prettyPrintTo (dest);
- return len;
- }
+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);
};
-#endif
-
-typedef std::function ArJsonRequestHandlerFunction;
-
-class AsyncCallbackJsonWebHandler: public AsyncWebHandler {
-private:
-protected:
- const String _uri;
- WebRequestMethodComposite _method;
- ArJsonRequestHandlerFunction _onRequest;
- size_t _contentLength;
-#ifndef ARDUINOJSON_5_COMPATIBILITY
- const size_t maxJsonBufferSize;
-#endif
- size_t _maxContentLength;
-public:
-#ifdef ARDUINOJSON_5_COMPATIBILITY
- AsyncCallbackJsonWebHandler(const String& uri, ArJsonRequestHandlerFunction onRequest)
- : _uri(uri), _method(HTTP_POST|HTTP_PUT|HTTP_PATCH), _onRequest(onRequest), _maxContentLength(16384) {}
-#else
- AsyncCallbackJsonWebHandler(const String& uri, ArJsonRequestHandlerFunction onRequest, size_t maxJsonBufferSize=DYNAMIC_JSON_DOCUMENT_SIZE)
- : _uri(uri), _method(HTTP_POST|HTTP_PUT|HTTP_PATCH), _onRequest(onRequest), maxJsonBufferSize(maxJsonBufferSize), _maxContentLength(16384) {}
-#endif
-
- void setMethod(WebRequestMethodComposite method){ _method = method; }
- void setMaxContentLength(int maxContentLength){ _maxContentLength = maxContentLength; }
- void onRequest(ArJsonRequestHandlerFunction fn){ _onRequest = fn; }
-
- virtual bool canHandle(AsyncWebServerRequest *request) override final{
- if(!_onRequest)
- return false;
-
- if(!(_method & request->method()))
- return false;
-
- if(_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri+"/")))
- return false;
-
- if ( !request->contentType().equalsIgnoreCase(JSON_MIMETYPE) )
- return false;
-
- request->addInterestingHeader("ANY");
- return true;
- }
-
- virtual void handleRequest(AsyncWebServerRequest *request) override final {
- if(_onRequest) {
- if (request->_tempObject != NULL) {
-
-#ifdef ARDUINOJSON_5_COMPATIBILITY
- DynamicJsonBuffer jsonBuffer;
- JsonVariant json = jsonBuffer.parse((uint8_t*)(request->_tempObject));
- if (json.success()) {
-#else
- DynamicJsonDocument jsonBuffer(this->maxJsonBufferSize);
- DeserializationError error = deserializeJson(jsonBuffer, (uint8_t*)(request->_tempObject));
- if(!error) {
- JsonVariant json = jsonBuffer.as();
-#endif
-
- _onRequest(request, json);
- return;
- }
- }
- request->send(_contentLength > _maxContentLength ? 413 : 400);
- } else {
- request->send(500);
- }
- }
- virtual void handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) override final {
- }
- virtual void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override final {
- if (_onRequest) {
- _contentLength = total;
- if (total > 0 && request->_tempObject == NULL && total < _maxContentLength) {
- request->_tempObject = malloc(total);
- }
- if (request->_tempObject != NULL) {
- memcpy((uint8_t*)(request->_tempObject) + index, data, len);
- }
- }
- }
- virtual bool isRequestHandlerTrivial() override final {return _onRequest ? false : true;}
+
+typedef std::function 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;
+
+ 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; }
+
+ 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
+
+#endif // ASYNC_JSON_SUPPORT == 1
+
+#endif // ASYNC_JSON_H_
diff --git a/src/AsyncMessagePack.cpp b/src/AsyncMessagePack.cpp
new file mode 100644
index 000000000..85af67148
--- /dev/null
+++ b/src/AsyncMessagePack.cpp
@@ -0,0 +1,102 @@
+#include "AsyncMessagePack.h"
+
+#if ASYNC_MSG_PACK_SUPPORT == 1
+
+ #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)
+ _root = _jsonBuffer.createNestedArray();
+ else
+ _root = _jsonBuffer.createNestedObject();
+}
+ #else
+AsyncMessagePackResponse::AsyncMessagePackResponse(bool isArray) : _isValid{false} {
+ _code = 200;
+ _contentType = asyncsrv::T_application_msgpack;
+ if (isArray)
+ _root = _jsonBuffer.add();
+ else
+ _root = _jsonBuffer.add();
+}
+ #endif
+
+size_t AsyncMessagePackResponse::setLength() {
+ _contentLength = measureMsgPack(_root);
+ if (_contentLength) {
+ _isValid = true;
+ }
+ return _contentLength;
+}
+
+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
+
+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 + "/")))
+ return false;
+
+ if (request->method() != HTTP_GET && !request->contentType().equalsIgnoreCase(asyncsrv::T_application_msgpack))
+ return false;
+
+ return true;
+}
+
+void AsyncCallbackMessagePackWebHandler::handleRequest(AsyncWebServerRequest* request) {
+ if (_onRequest) {
+ if (request->method() == HTTP_GET) {
+ JsonVariant json;
+ _onRequest(request, json);
+ return;
+ } else if (request->_tempObject != NULL) {
+
+ #if ARDUINOJSON_VERSION_MAJOR == 6
+ DynamicJsonDocument jsonBuffer(this->maxJsonBufferSize);
+ DeserializationError error = deserializeMsgPack(jsonBuffer, (uint8_t*)(request->_tempObject));
+ if (!error) {
+ JsonVariant json = jsonBuffer.as();
+ #else
+ JsonDocument jsonBuffer;
+ DeserializationError error = deserializeMsgPack(jsonBuffer, (uint8_t*)(request->_tempObject));
+ if (!error) {
+ JsonVariant json = jsonBuffer.as();
+ #endif
+
+ _onRequest(request, json);
+ return;
+ }
+ }
+ request->send(_contentLength > _maxContentLength ? 413 : 400);
+ } else {
+ request->send(500);
+ }
+}
+
+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) {
+ memcpy((uint8_t*)(request->_tempObject) + index, data, len);
+ }
+ }
+}
+
+#endif // ASYNC_MSG_PACK_SUPPORT
diff --git a/src/AsyncMessagePack.h b/src/AsyncMessagePack.h
new file mode 100644
index 000000000..78fde92fc
--- /dev/null
+++ b/src/AsyncMessagePack.h
@@ -0,0 +1,102 @@
+#pragma once
+
+/*
+ server.on("/msg_pack", HTTP_ANY, [](AsyncWebServerRequest * request) {
+ AsyncMessagePackResponse * response = new AsyncMessagePackResponse();
+ JsonObject& root = response->getRoot();
+ root["key1"] = "key number one";
+ JsonObject& nested = root.createNestedObject("nested");
+ nested["key1"] = "key number one";
+ response->setLength();
+ request->send(response);
+ });
+
+ --------------------
+
+ AsyncCallbackMessagePackWebHandler* handler = new AsyncCallbackMessagePackWebHandler("/msg_pack/endpoint");
+ handler->onRequest([](AsyncWebServerRequest *request, JsonVariant &json) {
+ JsonObject jsonObj = json.as();
+ // ...
+ });
+ server.addHandler(handler);
+*/
+
+#if __has_include("ArduinoJson.h")
+ #include
+ #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
+
+ #include "ChunkPrint.h"
+
+ #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
+
+ 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
+};
+
+typedef std::function 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;
+
+ 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; }
+
+ 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
diff --git a/src/AsyncWebHeader.cpp b/src/AsyncWebHeader.cpp
new file mode 100644
index 000000000..ba271a3b6
--- /dev/null
+++ b/src/AsyncWebHeader.cpp
@@ -0,0 +1,22 @@
+#include
+
+AsyncWebHeader::AsyncWebHeader(const String& data) {
+ if (!data)
+ return;
+ int index = data.indexOf(':');
+ 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);
+ return str;
+}
diff --git a/src/AsyncWebSocket.cpp b/src/AsyncWebSocket.cpp
index 9bd037a14..9deb74a9d 100644
--- a/src/AsyncWebSocket.cpp
+++ b/src/AsyncWebSocket.cpp
@@ -18,225 +18,142 @@
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
-#include "Arduino.h"
#include "AsyncWebSocket.h"
+#include "Arduino.h"
-#include
+#include
-#ifndef ESP8266
-extern "C" {
-typedef struct {
- uint32_t state[5];
- uint32_t count[2];
- unsigned char buffer[64];
-} SHA1_CTX;
+#include
-void SHA1Transform(uint32_t state[5], const unsigned char buffer[64]);
-void SHA1Init(SHA1_CTX* context);
-void SHA1Update(SHA1_CTX* context, const unsigned char* data, uint32_t len);
-void SHA1Final(unsigned char digest[20], SHA1_CTX* context);
-}
-#else
-#include
+#if defined(ESP32)
+ #if ESP_IDF_VERSION_MAJOR < 5
+ #include "BackPort_SHA1Builder.h"
+ #else
+ #include
+ #endif
+ #include
+#elif defined(TARGET_RP2040) || defined(ESP8266)
+ #include
#endif
-#define MAX_PRINTF_LEN 64
+using namespace asyncsrv;
-size_t webSocketSendFrameWindow(AsyncClient *client){
- if(!client->canSend())
+size_t webSocketSendFrameWindow(AsyncClient* client) {
+ if (!client || !client->canSend())
return 0;
size_t space = client->space();
- if(space < 9)
+ if (space < 9)
return 0;
return space - 8;
}
-size_t webSocketSendFrame(AsyncClient *client, bool final, uint8_t opcode, bool mask, uint8_t *data, size_t len){
- if(!client->canSend())
+size_t webSocketSendFrame(AsyncClient* client, bool final, uint8_t opcode, bool mask, uint8_t* data, size_t len) {
+ if (!client || !client->canSend()) {
+ // Serial.println("SF 1");
return 0;
+ }
size_t space = client->space();
- if(space < 2)
+ if (space < 2) {
+ // Serial.println("SF 2");
return 0;
- uint8_t mbuf[4] = {0,0,0,0};
+ }
+ uint8_t mbuf[4] = {0, 0, 0, 0};
uint8_t headLen = 2;
- if(len && mask){
+ if (len && mask) {
headLen += 4;
mbuf[0] = rand() % 0xFF;
mbuf[1] = rand() % 0xFF;
mbuf[2] = rand() % 0xFF;
mbuf[3] = rand() % 0xFF;
}
- if(len > 125)
+ if (len > 125)
headLen += 2;
- if(space < headLen)
+ if (space < headLen) {
+ // Serial.println("SF 2");
return 0;
+ }
space -= headLen;
- if(len > space) len = space;
+ if (len > space)
+ len = space;
- uint8_t *buf = (uint8_t*)malloc(headLen);
- if(buf == NULL){
- //os_printf("could not malloc %u bytes for frame header\n", headLen);
+ uint8_t* buf = (uint8_t*)malloc(headLen);
+ if (buf == NULL) {
+ // os_printf("could not malloc %u bytes for frame header\n", headLen);
+ // Serial.println("SF 3");
return 0;
}
buf[0] = opcode & 0x0F;
- if(final)
+ if (final)
buf[0] |= 0x80;
- if(len < 126)
+ if (len < 126)
buf[1] = len & 0x7F;
else {
buf[1] = 126;
buf[2] = (uint8_t)((len >> 8) & 0xFF);
buf[3] = (uint8_t)(len & 0xFF);
}
- if(len && mask){
+ if (len && mask) {
buf[1] |= 0x80;
memcpy(buf + (headLen - 4), mbuf, 4);
}
- if(client->add((const char *)buf, headLen) != headLen){
- //os_printf("error adding %lu header bytes\n", headLen);
+ if (client->add((const char*)buf, headLen) != headLen) {
+ // os_printf("error adding %lu header bytes\n", headLen);
free(buf);
+ // Serial.println("SF 4");
return 0;
}
free(buf);
- if(len){
- if(len && mask){
+ if (len) {
+ if (len && mask) {
size_t i;
- for(i=0;iadd((const char *)data, len) != len){
- //os_printf("error adding %lu data bytes\n", len);
+ if (client->add((const char*)data, len) != len) {
+ // os_printf("error adding %lu data bytes\n", len);
+ // Serial.println("SF 5");
return 0;
}
}
- if(!client->send()){
- //os_printf("error sending frame: %lu\n", headLen+len);
+ if (!client->send()) {
+ // os_printf("error sending frame: %lu\n", headLen+len);
+ // Serial.println("SF 6");
return 0;
}
+ // Serial.println("SF");
return len;
}
-
/*
* AsyncWebSocketMessageBuffer
*/
-
-
-AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer()
- :_data(nullptr)
- ,_len(0)
- ,_lock(false)
- ,_count(0)
-{
-
-}
-
-AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(uint8_t * data, size_t size)
- :_data(nullptr)
- ,_len(size)
- ,_lock(false)
- ,_count(0)
-{
-
- if (!data) {
- return;
- }
-
- _data = new uint8_t[_len + 1];
-
- if (_data) {
- memcpy(_data, data, _len);
- _data[_len] = 0;
+AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(const uint8_t* data, size_t size)
+ : _buffer(std::make_shared>(size)) {
+ if (_buffer->capacity() < size) {
+ _buffer->reserve(size);
+ } else {
+ std::memcpy(_buffer->data(), data, size);
}
}
-
AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(size_t size)
- :_data(nullptr)
- ,_len(size)
- ,_lock(false)
- ,_count(0)
-{
- _data = new uint8_t[_len + 1];
-
- if (_data) {
- _data[_len] = 0;
+ : _buffer(std::make_shared>(size)) {
+ if (_buffer->capacity() < size) {
+ _buffer->reserve(size);
}
-
-}
-
-AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(const AsyncWebSocketMessageBuffer & copy)
- :_data(nullptr)
- ,_len(0)
- ,_lock(false)
- ,_count(0)
-{
- _len = copy._len;
- _lock = copy._lock;
- _count = 0;
-
- if (_len) {
- _data = new uint8_t[_len + 1];
- _data[_len] = 0;
- }
-
- if (_data) {
- memcpy(_data, copy._data, _len);
- _data[_len] = 0;
- }
-
}
-AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(AsyncWebSocketMessageBuffer && copy)
- :_data(nullptr)
- ,_len(0)
- ,_lock(false)
- ,_count(0)
-{
- _len = copy._len;
- _lock = copy._lock;
- _count = 0;
-
- if (copy._data) {
- _data = copy._data;
- copy._data = nullptr;
- }
-
-}
-
-AsyncWebSocketMessageBuffer::~AsyncWebSocketMessageBuffer()
-{
- if (_data) {
- delete[] _data;
- }
+bool AsyncWebSocketMessageBuffer::reserve(size_t size) {
+ if (_buffer->capacity() >= size)
+ return true;
+ _buffer->reserve(size);
+ return _buffer->capacity() >= size;
}
-bool AsyncWebSocketMessageBuffer::reserve(size_t size)
-{
- _len = size;
-
- if (_data) {
- delete[] _data;
- _data = nullptr;
- }
-
- _data = new uint8_t[_len + 1];
-
- if (_data) {
- _data[_len] = 0;
- return true;
- } else {
- return false;
- }
-
-}
-
-
-
/*
* Control Frame
*/
@@ -244,239 +161,118 @@ bool AsyncWebSocketMessageBuffer::reserve(size_t size)
class AsyncWebSocketControl {
private:
uint8_t _opcode;
- uint8_t *_data;
+ uint8_t* _data;
size_t _len;
bool _mask;
bool _finished;
+
public:
- AsyncWebSocketControl(uint8_t opcode, uint8_t *data=NULL, size_t len=0, bool mask=false)
- :_opcode(opcode)
- ,_len(len)
- ,_mask(len && mask)
- ,_finished(false)
- {
- if(data == NULL)
+ AsyncWebSocketControl(uint8_t opcode, const uint8_t* data = NULL, size_t len = 0, bool mask = false)
+ : _opcode(opcode), _len(len), _mask(len && mask), _finished(false) {
+ if (data == NULL)
_len = 0;
- if(_len){
- if(_len > 125)
+ if (_len) {
+ if (_len > 125)
_len = 125;
+
_data = (uint8_t*)malloc(_len);
- if(_data == NULL)
+
+ if (_data == NULL)
_len = 0;
- else memcpy(_data, data, len);
- } else _data = NULL;
+ else
+ memcpy(_data, data, len);
+ } else
+ _data = NULL;
}
- virtual ~AsyncWebSocketControl(){
- if(_data != NULL)
+
+ ~AsyncWebSocketControl() {
+ if (_data != NULL)
free(_data);
}
- virtual bool finished() const { return _finished; }
- uint8_t opcode(){ return _opcode; }
- uint8_t len(){ return _len + 2; }
- size_t send(AsyncClient *client){
+
+ bool finished() const { return _finished; }
+ uint8_t opcode() { return _opcode; }
+ uint8_t len() { return _len + 2; }
+ size_t send(AsyncClient* client) {
_finished = true;
return webSocketSendFrame(client, true, _opcode & 0x0F, _mask, _data, _len);
}
};
/*
- * Basic Buffered Message
+ * AsyncWebSocketMessage Message
*/
-
-AsyncWebSocketBasicMessage::AsyncWebSocketBasicMessage(const char * data, size_t len, uint8_t opcode, bool mask)
- :_len(len)
- ,_sent(0)
- ,_ack(0)
- ,_acked(0)
-{
- _opcode = opcode & 0x07;
- _mask = mask;
- _data = (uint8_t*)malloc(_len+1);
- if(_data == NULL){
- _len = 0;
- _status = WS_MSG_ERROR;
- } else {
- _status = WS_MSG_SENDING;
- memcpy(_data, data, _len);
- _data[_len] = 0;
- }
-}
-AsyncWebSocketBasicMessage::AsyncWebSocketBasicMessage(uint8_t opcode, bool mask)
- :_len(0)
- ,_sent(0)
- ,_ack(0)
- ,_acked(0)
- ,_data(NULL)
-{
- _opcode = opcode & 0x07;
- _mask = mask;
-
-}
-
-
-AsyncWebSocketBasicMessage::~AsyncWebSocketBasicMessage() {
- if(_data != NULL)
- free(_data);
+AsyncWebSocketMessage::AsyncWebSocketMessage(AsyncWebSocketSharedBuffer buffer, uint8_t opcode, bool mask) : _WSbuffer{buffer},
+ _opcode(opcode & 0x07),
+ _mask{mask},
+ _status{_WSbuffer ? WS_MSG_SENDING : WS_MSG_ERROR} {
}
- void AsyncWebSocketBasicMessage::ack(size_t len, uint32_t time) {
+void AsyncWebSocketMessage::ack(size_t len, uint32_t time) {
+ (void)time;
_acked += len;
- if(_sent == _len && _acked == _ack){
+ if (_sent >= _WSbuffer->size() && _acked >= _ack) {
_status = WS_MSG_SENT;
}
+ // ets_printf("A: %u\n", len);
}
- size_t AsyncWebSocketBasicMessage::send(AsyncClient *client) {
- if(_status != WS_MSG_SENDING)
+
+size_t AsyncWebSocketMessage::send(AsyncClient* client) {
+ if (!client)
+ return 0;
+
+ if (_status != WS_MSG_SENDING)
return 0;
- if(_acked < _ack){
+ if (_acked < _ack) {
return 0;
}
- if(_sent == _len){
- if(_acked == _ack)
+ if (_sent == _WSbuffer->size()) {
+ if (_acked == _ack)
_status = WS_MSG_SENT;
return 0;
}
- if(_sent > _len){
- _status = WS_MSG_ERROR;
- return 0;
- }
-
- size_t toSend = _len - _sent;
- size_t window = webSocketSendFrameWindow(client);
-
- if(window < toSend) {
- toSend = window;
- }
-
- _sent += toSend;
- _ack += toSend + ((toSend < 126)?2:4) + (_mask * 4);
-
- bool final = (_sent == _len);
- uint8_t* dPtr = (uint8_t*)(_data + (_sent - toSend));
- uint8_t opCode = (toSend && _sent == toSend)?_opcode:(uint8_t)WS_CONTINUATION;
-
- size_t sent = webSocketSendFrame(client, final, opCode, _mask, dPtr, toSend);
- _status = WS_MSG_SENDING;
- if(toSend && sent != toSend){
- _sent -= (toSend - sent);
- _ack -= (toSend - sent);
- }
- return sent;
-}
-
-// bool AsyncWebSocketBasicMessage::reserve(size_t size) {
-// if (size) {
-// _data = (uint8_t*)malloc(size +1);
-// if (_data) {
-// memset(_data, 0, size);
-// _len = size;
-// _status = WS_MSG_SENDING;
-// return true;
-// }
-// }
-// return false;
-// }
-
-
-/*
- * AsyncWebSocketMultiMessage Message
- */
-
-
-AsyncWebSocketMultiMessage::AsyncWebSocketMultiMessage(AsyncWebSocketMessageBuffer * buffer, uint8_t opcode, bool mask)
- :_len(0)
- ,_sent(0)
- ,_ack(0)
- ,_acked(0)
- ,_WSbuffer(nullptr)
-{
-
- _opcode = opcode & 0x07;
- _mask = mask;
-
- if (buffer) {
- _WSbuffer = buffer;
- (*_WSbuffer)++;
- _data = buffer->get();
- _len = buffer->length();
- _status = WS_MSG_SENDING;
- //ets_printf("M: %u\n", _len);
- } else {
+ if (_sent > _WSbuffer->size()) {
_status = WS_MSG_ERROR;
- }
-
-}
-
-
-AsyncWebSocketMultiMessage::~AsyncWebSocketMultiMessage() {
- if (_WSbuffer) {
- (*_WSbuffer)--; // decreases the counter.
- }
-}
-
- void AsyncWebSocketMultiMessage::ack(size_t len, uint32_t time) {
- _acked += len;
- if(_sent >= _len && _acked >= _ack){
- _status = WS_MSG_SENT;
- }
- //ets_printf("A: %u\n", len);
-}
- size_t AsyncWebSocketMultiMessage::send(AsyncClient *client) {
- if(_status != WS_MSG_SENDING)
- return 0;
- if(_acked < _ack){
+ // ets_printf("E: %u > %u\n", _sent, _WSbuffer->length());
return 0;
}
- if(_sent == _len){
- _status = WS_MSG_SENT;
- return 0;
- }
- if(_sent > _len){
- _status = WS_MSG_ERROR;
- //ets_printf("E: %u > %u\n", _sent, _len);
- return 0;
- }
- size_t toSend = _len - _sent;
+ size_t toSend = _WSbuffer->size() - _sent;
size_t window = webSocketSendFrameWindow(client);
- if(window < toSend) {
- toSend = window;
+ if (window < toSend) {
+ toSend = window;
}
_sent += toSend;
- _ack += toSend + ((toSend < 126)?2:4) + (_mask * 4);
+ _ack += toSend + ((toSend < 126) ? 2 : 4) + (_mask * 4);
- //ets_printf("W: %u %u\n", _sent - toSend, toSend);
+ // ets_printf("W: %u %u\n", _sent - toSend, toSend);
- bool final = (_sent == _len);
- uint8_t* dPtr = (uint8_t*)(_data + (_sent - toSend));
- uint8_t opCode = (toSend && _sent == toSend)?_opcode:(uint8_t)WS_CONTINUATION;
+ bool final = (_sent == _WSbuffer->size());
+ uint8_t* dPtr = (uint8_t*)(_WSbuffer->data() + (_sent - toSend));
+ uint8_t opCode = (toSend && _sent == toSend) ? _opcode : (uint8_t)WS_CONTINUATION;
size_t sent = webSocketSendFrame(client, final, opCode, _mask, dPtr, toSend);
_status = WS_MSG_SENDING;
- if(toSend && sent != toSend){
- //ets_printf("E: %u != %u\n", toSend, sent);
- _sent -= (toSend - sent);
- _ack -= (toSend - sent);
+ if (toSend && sent != toSend) {
+ // ets_printf("E: %u != %u\n", toSend, sent);
+ _sent -= (toSend - sent);
+ _ack -= (toSend - sent);
}
- //ets_printf("S: %u %u\n", _sent, sent);
+ // ets_printf("S: %u %u\n", _sent, sent);
return sent;
}
-
/*
* Async WebSocket Client
*/
- const char * AWSC_PING_PAYLOAD = "ESPAsyncWebServer-PING";
- const size_t AWSC_PING_PAYLOAD_LEN = 22;
-
-AsyncWebSocketClient::AsyncWebSocketClient(AsyncWebServerRequest *request, AsyncWebSocket *server)
- : _controlQueue(LinkedList([](AsyncWebSocketControl *c){ delete c; }))
- , _messageQueue(LinkedList([](AsyncWebSocketMessage *m){ delete m; }))
- , _tempObject(NULL)
-{
+const char* AWSC_PING_PAYLOAD = "ESPAsyncWebServer-PING";
+const size_t AWSC_PING_PAYLOAD_LEN = 22;
+
+AsyncWebSocketClient::AsyncWebSocketClient(AsyncWebServerRequest* request, AsyncWebSocket* server)
+ : _tempObject(NULL) {
_client = request->client();
_server = server;
_clientId = _server->_getNextId();
@@ -485,160 +281,253 @@ AsyncWebSocketClient::AsyncWebSocketClient(AsyncWebServerRequest *request, Async
_lastMessageTime = millis();
_keepAlivePeriod = 0;
_client->setRxTimeout(0);
- _client->onError([](void *r, AsyncClient* c, int8_t error){ ((AsyncWebSocketClient*)(r))->_onError(error); }, this);
- _client->onAck([](void *r, AsyncClient* c, size_t len, uint32_t time){ ((AsyncWebSocketClient*)(r))->_onAck(len, time); }, this);
- _client->onDisconnect([](void *r, AsyncClient* c){ ((AsyncWebSocketClient*)(r))->_onDisconnect(); delete c; }, this);
- _client->onTimeout([](void *r, AsyncClient* c, uint32_t time){ ((AsyncWebSocketClient*)(r))->_onTimeout(time); }, this);
- _client->onData([](void *r, AsyncClient* c, void *buf, size_t len){ ((AsyncWebSocketClient*)(r))->_onData(buf, len); }, this);
- _client->onPoll([](void *r, AsyncClient* c){ ((AsyncWebSocketClient*)(r))->_onPoll(); }, this);
- _server->_addClient(this);
- _server->_handleEvent(this, WS_EVT_CONNECT, NULL, NULL, 0);
+ _client->onError([](void* r, AsyncClient* c, int8_t error) { (void)c; ((AsyncWebSocketClient*)(r))->_onError(error); }, this);
+ _client->onAck([](void* r, AsyncClient* c, size_t len, uint32_t time) { (void)c; ((AsyncWebSocketClient*)(r))->_onAck(len, time); }, this);
+ _client->onDisconnect([](void* r, AsyncClient* c) { ((AsyncWebSocketClient*)(r))->_onDisconnect(); delete c; }, this);
+ _client->onTimeout([](void* r, AsyncClient* c, uint32_t time) { (void)c; ((AsyncWebSocketClient*)(r))->_onTimeout(time); }, this);
+ _client->onData([](void* r, AsyncClient* c, void* buf, size_t len) { (void)c; ((AsyncWebSocketClient*)(r))->_onData(buf, len); }, this);
+ _client->onPoll([](void* r, AsyncClient* c) { (void)c; ((AsyncWebSocketClient*)(r))->_onPoll(); }, this);
delete request;
+ memset(&_pinfo, 0, sizeof(_pinfo));
}
-AsyncWebSocketClient::~AsyncWebSocketClient(){
- _messageQueue.free();
- _controlQueue.free();
+AsyncWebSocketClient::~AsyncWebSocketClient() {
+ {
+#ifdef ESP32
+ std::lock_guard lock(_lock);
+#endif
+ _messageQueue.clear();
+ _controlQueue.clear();
+ }
_server->_handleEvent(this, WS_EVT_DISCONNECT, NULL, NULL, 0);
}
-void AsyncWebSocketClient::_onAck(size_t len, uint32_t time){
+void AsyncWebSocketClient::_clearQueue() {
+ while (!_messageQueue.empty() && _messageQueue.front().finished())
+ _messageQueue.pop_front();
+}
+
+void AsyncWebSocketClient::_onAck(size_t len, uint32_t time) {
_lastMessageTime = millis();
- if(!_controlQueue.isEmpty()){
- auto head = _controlQueue.front();
- if(head->finished()){
- len -= head->len();
- if(_status == WS_DISCONNECTING && head->opcode() == WS_DISCONNECT){
- _controlQueue.remove(head);
+
+#ifdef ESP32
+ std::lock_guard lock(_lock);
+#endif
+
+ if (!_controlQueue.empty()) {
+ auto& head = _controlQueue.front();
+ if (head.finished()) {
+ len -= head.len();
+ if (_status == WS_DISCONNECTING && head.opcode() == WS_DISCONNECT) {
+ _controlQueue.pop_front();
_status = WS_DISCONNECTED;
- _client->close(true);
+ if (_client)
+ _client->close(true);
return;
}
- _controlQueue.remove(head);
+ _controlQueue.pop_front();
}
}
- if(len && !_messageQueue.isEmpty()){
- _messageQueue.front()->ack(len, time);
+
+ if (len && !_messageQueue.empty()) {
+ _messageQueue.front().ack(len, time);
}
- _server->_cleanBuffers();
+
+ _clearQueue();
+
_runQueue();
}
-void AsyncWebSocketClient::_onPoll(){
- if(_client->canSend() && (!_controlQueue.isEmpty() || !_messageQueue.isEmpty())){
+void AsyncWebSocketClient::_onPoll() {
+ if (!_client)
+ return;
+
+#ifdef ESP32
+ std::unique_lock lock(_lock);
+#endif
+ if (_client && _client->canSend() && (!_controlQueue.empty() || !_messageQueue.empty())) {
_runQueue();
- } else if(_keepAlivePeriod > 0 && _controlQueue.isEmpty() && _messageQueue.isEmpty() && (millis() - _lastMessageTime) >= _keepAlivePeriod){
- ping((uint8_t *)AWSC_PING_PAYLOAD, AWSC_PING_PAYLOAD_LEN);
+ } else if (_keepAlivePeriod > 0 && (millis() - _lastMessageTime) >= _keepAlivePeriod && (_controlQueue.empty() && _messageQueue.empty())) {
+#ifdef ESP32
+ lock.unlock();
+#endif
+ ping((uint8_t*)AWSC_PING_PAYLOAD, AWSC_PING_PAYLOAD_LEN);
}
}
-void AsyncWebSocketClient::_runQueue(){
- while(!_messageQueue.isEmpty() && _messageQueue.front()->finished()){
- _messageQueue.remove(_messageQueue.front());
- }
+void AsyncWebSocketClient::_runQueue() {
+ // all calls to this method MUST be protected by a mutex lock!
+ if (!_client)
+ return;
+
+ _clearQueue();
- if(!_controlQueue.isEmpty() && (_messageQueue.isEmpty() || _messageQueue.front()->betweenFrames()) && webSocketSendFrameWindow(_client) > (size_t)(_controlQueue.front()->len() - 1)){
- _controlQueue.front()->send(_client);
- } else if(!_messageQueue.isEmpty() && _messageQueue.front()->betweenFrames() && webSocketSendFrameWindow(_client)){
- _messageQueue.front()->send(_client);
+ if (!_controlQueue.empty() && (_messageQueue.empty() || _messageQueue.front().betweenFrames()) && webSocketSendFrameWindow(_client) > (size_t)(_controlQueue.front().len() - 1)) {
+ _controlQueue.front().send(_client);
+ } else if (!_messageQueue.empty() && _messageQueue.front().betweenFrames() && webSocketSendFrameWindow(_client)) {
+ _messageQueue.front().send(_client);
}
}
-bool AsyncWebSocketClient::queueIsFull(){
- if((_messageQueue.length() >= WS_MAX_QUEUED_MESSAGES) || (_status != WS_CONNECTED) ) return true;
- return false;
+bool AsyncWebSocketClient::queueIsFull() const {
+#ifdef ESP32
+ std::lock_guard lock(_lock);
+#endif
+ return (_messageQueue.size() >= WS_MAX_QUEUED_MESSAGES) || (_status != WS_CONNECTED);
}
-void AsyncWebSocketClient::_queueMessage(AsyncWebSocketMessage *dataMessage){
- if(dataMessage == NULL)
- return;
- if(_status != WS_CONNECTED){
- delete dataMessage;
- return;
- }
- if(_messageQueue.length() >= WS_MAX_QUEUED_MESSAGES){
- ets_printf("ERROR: Too many messages queued\n");
- delete dataMessage;
- } else {
- _messageQueue.add(dataMessage);
- }
- if(_client->canSend())
+size_t AsyncWebSocketClient::queueLen() const {
+#ifdef ESP32
+ std::lock_guard lock(_lock);
+#endif
+ return _messageQueue.size();
+}
+
+bool AsyncWebSocketClient::canSend() const {
+#ifdef ESP32
+ std::lock_guard lock(_lock);
+#endif
+ return _messageQueue.size() < WS_MAX_QUEUED_MESSAGES;
+}
+
+bool AsyncWebSocketClient::_queueControl(uint8_t opcode, const uint8_t* data, size_t len, bool mask) {
+ if (!_client)
+ return false;
+
+#ifdef ESP32
+ std::lock_guard lock(_lock);
+#endif
+
+ _controlQueue.emplace_back(opcode, data, len, mask);
+
+ if (_client && _client->canSend())
_runQueue();
+
+ return true;
}
-void AsyncWebSocketClient::_queueControl(AsyncWebSocketControl *controlMessage){
- if(controlMessage == NULL)
- return;
- _controlQueue.add(controlMessage);
- if(_client->canSend())
+bool AsyncWebSocketClient::_queueMessage(AsyncWebSocketSharedBuffer buffer, uint8_t opcode, bool mask) {
+ if (!_client || buffer->size() == 0 || _status != WS_CONNECTED)
+ return false;
+
+#ifdef ESP32
+ std::lock_guard lock(_lock);
+#endif
+
+ if (_messageQueue.size() >= WS_MAX_QUEUED_MESSAGES) {
+ if (closeWhenFull) {
+ _status = WS_DISCONNECTED;
+
+ if (_client)
+ _client->close(true);
+
+#ifdef ESP8266
+ ets_printf("AsyncWebSocketClient::_queueMessage: Too many messages queued: closing connection\n");
+#elif defined(ESP32)
+ log_e("Too many messages queued: closing connection");
+#endif
+
+ } else {
+#ifdef ESP8266
+ ets_printf("AsyncWebSocketClient::_queueMessage: Too many messages queued: discarding new message\n");
+#elif defined(ESP32)
+ log_e("Too many messages queued: discarding new message");
+#endif
+ }
+
+ return false;
+ }
+
+ _messageQueue.emplace_back(buffer, opcode, mask);
+
+ if (_client && _client->canSend())
_runQueue();
+
+ return true;
}
-void AsyncWebSocketClient::close(uint16_t code, const char * message){
- if(_status != WS_CONNECTED)
+void AsyncWebSocketClient::close(uint16_t code, const char* message) {
+ if (_status != WS_CONNECTED)
return;
- if(code){
+
+ _status = WS_DISCONNECTING;
+
+ if (code) {
uint8_t packetLen = 2;
- if(message != NULL){
+ if (message != NULL) {
size_t mlen = strlen(message);
- if(mlen > 123) mlen = 123;
+ if (mlen > 123)
+ mlen = 123;
packetLen += mlen;
}
- char * buf = (char*)malloc(packetLen);
- if(buf != NULL){
+ char* buf = (char*)malloc(packetLen);
+ if (buf != NULL) {
buf[0] = (uint8_t)(code >> 8);
buf[1] = (uint8_t)(code & 0xFF);
- if(message != NULL){
- memcpy(buf+2, message, packetLen -2);
+ if (message != NULL) {
+ memcpy(buf + 2, message, packetLen - 2);
}
- _queueControl(new AsyncWebSocketControl(WS_DISCONNECT,(uint8_t*)buf,packetLen));
+ _queueControl(WS_DISCONNECT, (uint8_t*)buf, packetLen);
free(buf);
return;
}
}
- _queueControl(new AsyncWebSocketControl(WS_DISCONNECT));
+ _queueControl(WS_DISCONNECT);
}
-void AsyncWebSocketClient::ping(uint8_t *data, size_t len){
- if(_status == WS_CONNECTED)
- _queueControl(new AsyncWebSocketControl(WS_PING, data, len));
+bool AsyncWebSocketClient::ping(const uint8_t* data, size_t len) {
+ return _status == WS_CONNECTED && _queueControl(WS_PING, data, len);
}
-void AsyncWebSocketClient::_onError(int8_t){}
+void AsyncWebSocketClient::_onError(int8_t) {
+ // Serial.println("onErr");
+}
-void AsyncWebSocketClient::_onTimeout(uint32_t time){
+void AsyncWebSocketClient::_onTimeout(uint32_t time) {
+ if (!_client)
+ return;
+ // Serial.println("onTime");
+ (void)time;
_client->close(true);
}
-void AsyncWebSocketClient::_onDisconnect(){
- _client = NULL;
- _server->_handleDisconnect(this);
+void AsyncWebSocketClient::_onDisconnect() {
+ // Serial.println("onDis");
+ _client = nullptr;
}
-void AsyncWebSocketClient::_onData(void *pbuf, size_t plen){
+void AsyncWebSocketClient::_onData(void* pbuf, size_t plen) {
_lastMessageTime = millis();
- uint8_t *data = (uint8_t*)pbuf;
- while(plen > 0){
- if(!_pstate){
- const uint8_t *fdata = data;
+ uint8_t* data = (uint8_t*)pbuf;
+ while (plen > 0) {
+ if (!_pstate) {
+ const uint8_t* fdata = data;
+
_pinfo.index = 0;
_pinfo.final = (fdata[0] & 0x80) != 0;
_pinfo.opcode = fdata[0] & 0x0F;
_pinfo.masked = (fdata[1] & 0x80) != 0;
_pinfo.len = fdata[1] & 0x7F;
+
+ // log_d("WS[%" PRIu32 "]: _onData: %" PRIu32, _clientId, plen);
+ // log_d("WS[%" PRIu32 "]: _status = %" PRIu32, _clientId, _status);
+ // log_d("WS[%" PRIu32 "]: _pinfo: index: %" PRIu64 ", final: %" PRIu8 ", opcode: %" PRIu8 ", masked: %" PRIu8 ", len: %" PRIu64, _clientId, _pinfo.index, _pinfo.final, _pinfo.opcode, _pinfo.masked, _pinfo.len);
+
data += 2;
plen -= 2;
- if(_pinfo.len == 126){
+
+ if (_pinfo.len == 126 && plen >= 2) {
_pinfo.len = fdata[3] | (uint16_t)(fdata[2]) << 8;
data += 2;
plen -= 2;
- } else if(_pinfo.len == 127){
+
+ } else if (_pinfo.len == 127 && plen >= 8) {
_pinfo.len = fdata[9] | (uint16_t)(fdata[8]) << 8 | (uint32_t)(fdata[7]) << 16 | (uint32_t)(fdata[6]) << 24 | (uint64_t)(fdata[5]) << 32 | (uint64_t)(fdata[4]) << 40 | (uint64_t)(fdata[3]) << 48 | (uint64_t)(fdata[2]) << 56;
data += 8;
plen -= 8;
}
- if(_pinfo.masked){
+ if (_pinfo.masked && plen >= 4) { // if ws.close() is called, Safari sends a close frame with plen 2 and masked bit set. We must not decrement plen which is already 0.
memcpy(_pinfo.mask, data, 4);
data += 4;
plen -= 4;
@@ -648,56 +537,65 @@ void AsyncWebSocketClient::_onData(void *pbuf, size_t plen){
const size_t datalen = std::min((size_t)(_pinfo.len - _pinfo.index), plen);
const auto datalast = data[datalen];
- if(_pinfo.masked){
- for(size_t i=0;i_handleEvent(this, WS_EVT_DATA, (void *)&_pinfo, (uint8_t*)data, datalen);
+ if (datalen > 0)
+ _server->_handleEvent(this, WS_EVT_DATA, (void*)&_pinfo, data, datalen);
_pinfo.index += datalen;
- } else if((datalen + _pinfo.index) == _pinfo.len){
+ } else if ((datalen + _pinfo.index) == _pinfo.len) {
_pstate = 0;
- if(_pinfo.opcode == WS_DISCONNECT){
- if(datalen){
+ if (_pinfo.opcode == WS_DISCONNECT) {
+ if (datalen) {
uint16_t reasonCode = (uint16_t)(data[0] << 8) + data[1];
- char * reasonString = (char*)(data+2);
- if(reasonCode > 1001){
- _server->_handleEvent(this, WS_EVT_ERROR, (void *)&reasonCode, (uint8_t*)reasonString, strlen(reasonString));
+ char* reasonString = (char*)(data + 2);
+ if (reasonCode > 1001) {
+ _server->_handleEvent(this, WS_EVT_ERROR, (void*)&reasonCode, (uint8_t*)reasonString, strlen(reasonString));
}
}
- if(_status == WS_DISCONNECTING){
+ if (_status == WS_DISCONNECTING) {
_status = WS_DISCONNECTED;
- _client->close(true);
+ if (_client)
+ _client->close(true);
} else {
_status = WS_DISCONNECTING;
- _queueControl(new AsyncWebSocketControl(WS_DISCONNECT, data, datalen));
+ if (_client)
+ _client->ackLater();
+ _queueControl(WS_DISCONNECT, data, datalen);
}
- } else if(_pinfo.opcode == WS_PING){
- _queueControl(new AsyncWebSocketControl(WS_PONG, data, datalen));
- } else if(_pinfo.opcode == WS_PONG){
- if(datalen != AWSC_PING_PAYLOAD_LEN || memcmp(AWSC_PING_PAYLOAD, data, AWSC_PING_PAYLOAD_LEN) != 0)
- _server->_handleEvent(this, WS_EVT_PONG, NULL, data, datalen);
- } else if(_pinfo.opcode < 8){//continuation or text/binary frame
- _server->_handleEvent(this, WS_EVT_DATA, (void *)&_pinfo, data, datalen);
+ } else if (_pinfo.opcode == WS_PING) {
+ _server->_handleEvent(this, WS_EVT_PING, NULL, NULL, 0);
+ _queueControl(WS_PONG, data, datalen);
+ } else if (_pinfo.opcode == WS_PONG) {
+ if (datalen != AWSC_PING_PAYLOAD_LEN || memcmp(AWSC_PING_PAYLOAD, data, AWSC_PING_PAYLOAD_LEN) != 0)
+ _server->_handleEvent(this, WS_EVT_PONG, NULL, NULL, 0);
+ } else if (_pinfo.opcode < WS_DISCONNECT) { // continuation or text/binary frame
+ _server->_handleEvent(this, WS_EVT_DATA, (void*)&_pinfo, data, datalen);
+ if (_pinfo.final)
+ _pinfo.num = 0;
+ else
+ _pinfo.num += 1;
}
} else {
- //os_printf("frame error: len: %u, index: %llu, total: %llu\n", datalen, _pinfo.index, _pinfo.len);
- //what should we do?
+ // os_printf("frame error: len: %u, index: %llu, total: %llu\n", datalen, _pinfo.index, _pinfo.len);
+ // what should we do?
break;
}
// restore byte as _handleEvent may have added a null terminator i.e., data[len] = 0;
- if (datalen > 0)
+ if (datalen)
data[datalen] = datalast;
data += datalen;
@@ -705,302 +603,443 @@ void AsyncWebSocketClient::_onData(void *pbuf, size_t plen){
}
}
-size_t AsyncWebSocketClient::printf(const char *format, ...) {
+size_t AsyncWebSocketClient::printf(const char* format, ...) {
va_list arg;
va_start(arg, format);
- char* temp = new char[MAX_PRINTF_LEN];
- if(!temp){
+ size_t len = vsnprintf(nullptr, 0, format, arg);
+ va_end(arg);
+
+ if (len == 0)
return 0;
- }
- char* buffer = temp;
- size_t len = vsnprintf(temp, MAX_PRINTF_LEN, format, arg);
+
+ char* buffer = new char[len + 1];
+
+ if (!buffer)
+ return 0;
+
+ va_start(arg, format);
+ len = vsnprintf(buffer, len + 1, format, arg);
va_end(arg);
- if (len > (MAX_PRINTF_LEN - 1)) {
- buffer = new char[len + 1];
- if (!buffer) {
- delete[] temp;
- return 0;
- }
- va_start(arg, format);
- vsnprintf(buffer, len + 1, format, arg);
- va_end(arg);
- }
- text(buffer, len);
- if (buffer != temp) {
- delete[] buffer;
- }
- delete[] temp;
- return len;
+ bool enqueued = text(buffer, len);
+ delete[] buffer;
+ return enqueued ? len : 0;
}
-#ifndef ESP32
+#ifdef ESP8266
size_t AsyncWebSocketClient::printf_P(PGM_P formatP, ...) {
va_list arg;
va_start(arg, formatP);
- char* temp = new char[MAX_PRINTF_LEN];
- if(!temp){
+ size_t len = vsnprintf_P(nullptr, 0, formatP, arg);
+ va_end(arg);
+
+ if (len == 0)
return 0;
- }
- char* buffer = temp;
- size_t len = vsnprintf_P(temp, MAX_PRINTF_LEN, formatP, arg);
+
+ char* buffer = new char[len + 1];
+
+ if (!buffer)
+ return 0;
+
+ va_start(arg, formatP);
+ len = vsnprintf_P(buffer, len + 1, formatP, arg);
va_end(arg);
- if (len > (MAX_PRINTF_LEN - 1)) {
- buffer = new char[len + 1];
- if (!buffer) {
- delete[] temp;
- return 0;
- }
- va_start(arg, formatP);
- vsnprintf_P(buffer, len + 1, formatP, arg);
- va_end(arg);
+ bool enqueued = text(buffer, len);
+ delete[] buffer;
+ return enqueued ? len : 0;
+}
+#endif
+
+namespace {
+ AsyncWebSocketSharedBuffer makeSharedBuffer(const uint8_t* message, size_t len) {
+ auto buffer = std::make_shared>(len);
+ std::memcpy(buffer->data(), message, len);
+ return buffer;
}
- text(buffer, len);
- if (buffer != temp) {
- delete[] buffer;
+}
+
+bool AsyncWebSocketClient::text(AsyncWebSocketMessageBuffer* buffer) {
+ bool enqueued = false;
+ if (buffer) {
+ enqueued = text(std::move(buffer->_buffer));
+ delete buffer;
}
- delete[] temp;
- return len;
+ return enqueued;
}
-#endif
-void AsyncWebSocketClient::text(const char * message, size_t len){
- _queueMessage(new AsyncWebSocketBasicMessage(message, len));
+bool AsyncWebSocketClient::text(AsyncWebSocketSharedBuffer buffer) {
+ return _queueMessage(buffer);
}
-void AsyncWebSocketClient::text(const char * message){
- text(message, strlen(message));
+
+bool AsyncWebSocketClient::text(const uint8_t* message, size_t len) {
+ return text(makeSharedBuffer(message, len));
}
-void AsyncWebSocketClient::text(uint8_t * message, size_t len){
- text((const char *)message, len);
+
+bool AsyncWebSocketClient::text(const char* message, size_t len) {
+ return text((const uint8_t*)message, len);
}
-void AsyncWebSocketClient::text(char * message){
- text(message, strlen(message));
+
+bool AsyncWebSocketClient::text(const char* message) {
+ return text(message, strlen(message));
}
-void AsyncWebSocketClient::text(const String &message){
- text(message.c_str(), message.length());
+
+bool AsyncWebSocketClient::text(const String& message) {
+ return text(message.c_str(), message.length());
}
-void AsyncWebSocketClient::text(const __FlashStringHelper *data){
+
+#ifdef ESP8266
+bool AsyncWebSocketClient::text(const __FlashStringHelper* data) {
PGM_P p = reinterpret_cast(data);
+
size_t n = 0;
while (1) {
- if (pgm_read_byte(p+n) == 0) break;
- n += 1;
+ if (pgm_read_byte(p + n) == 0)
+ break;
+ n += 1;
}
- char * message = (char*) malloc(n+1);
- if(message){
- for(size_t b=0; b_buffer));
+ delete buffer;
+ }
+ return enqueued;
}
-void AsyncWebSocketClient::binary(const char * message, size_t len){
- _queueMessage(new AsyncWebSocketBasicMessage(message, len, WS_BINARY));
+bool AsyncWebSocketClient::binary(AsyncWebSocketSharedBuffer buffer) {
+ return _queueMessage(buffer, WS_BINARY);
}
-void AsyncWebSocketClient::binary(const char * message){
- binary(message, strlen(message));
+
+bool AsyncWebSocketClient::binary(const uint8_t* message, size_t len) {
+ return binary(makeSharedBuffer(message, len));
}
-void AsyncWebSocketClient::binary(uint8_t * message, size_t len){
- binary((const char *)message, len);
+
+bool AsyncWebSocketClient::binary(const char* message, size_t len) {
+ return binary((const uint8_t*)message, len);
}
-void AsyncWebSocketClient::binary(char * message){
- binary(message, strlen(message));
+
+bool AsyncWebSocketClient::binary(const char* message) {
+ return binary(message, strlen(message));
}
-void AsyncWebSocketClient::binary(const String &message){
- binary(message.c_str(), message.length());
+
+bool AsyncWebSocketClient::binary(const String& message) {
+ return binary(message.c_str(), message.length());
}
-void AsyncWebSocketClient::binary(const __FlashStringHelper *data, size_t len){
+
+#ifdef ESP8266
+bool AsyncWebSocketClient::binary(const __FlashStringHelper* data, size_t len) {
PGM_P p = reinterpret_cast(data);
- char * message = (char*) malloc(len);
- if(message){
- for(size_t b=0; bremoteIP();
-}
+IPAddress AsyncWebSocketClient::remoteIP() const {
+ if (!_client)
+ return IPAddress((uint32_t)0U);
-uint16_t AsyncWebSocketClient::remotePort() {
- if(!_client) {
- return 0;
- }
- return _client->remotePort();
+ return _client->remoteIP();
}
+uint16_t AsyncWebSocketClient::remotePort() const {
+ if (!_client)
+ return 0;
+ return _client->remotePort();
+}
/*
* Async Web Socket - Each separate socket location
*/
-AsyncWebSocket::AsyncWebSocket(const String& url)
- :_url(url)
- ,_clients(LinkedList([](AsyncWebSocketClient *c){ delete c; }))
- ,_cNextId(1)
- ,_enabled(true)
- ,_buffers(LinkedList([](AsyncWebSocketMessageBuffer *b){ delete b; }))
-{
- _eventHandler = NULL;
+void AsyncWebSocket::_handleEvent(AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len) {
+ if (_eventHandler != NULL) {
+ _eventHandler(this, client, type, arg, data, len);
+ }
}
-AsyncWebSocket::~AsyncWebSocket(){}
+AsyncWebSocketClient* AsyncWebSocket::_newClient(AsyncWebServerRequest* request) {
+ _clients.emplace_back(request, this);
+ _handleEvent(&_clients.back(), WS_EVT_CONNECT, request, NULL, 0);
+ return &_clients.back();
+}
-void AsyncWebSocket::_handleEvent(AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){
- if(_eventHandler != NULL){
- _eventHandler(this, client, type, arg, data, len);
- }
+bool AsyncWebSocket::availableForWriteAll() {
+ return std::none_of(std::begin(_clients), std::end(_clients), [](const AsyncWebSocketClient& c) { return c.queueIsFull(); });
}
-void AsyncWebSocket::_addClient(AsyncWebSocketClient * client){
- _clients.add(client);
+bool AsyncWebSocket::availableForWrite(uint32_t id) {
+ const auto iter = std::find_if(std::begin(_clients), std::end(_clients), [id](const AsyncWebSocketClient& c) { return c.id() == id; });
+ if (iter == std::end(_clients))
+ return true;
+ return !iter->queueIsFull();
}
-void AsyncWebSocket::_handleDisconnect(AsyncWebSocketClient * client){
-
- _clients.remove_first([=](AsyncWebSocketClient * c){
- return c->id() == client->id();
- });
+size_t AsyncWebSocket::count() const {
+ return std::count_if(std::begin(_clients), std::end(_clients), [](const AsyncWebSocketClient& c) { return c.status() == WS_CONNECTED; });
}
-bool AsyncWebSocket::availableForWriteAll(){
- for(const auto& c: _clients){
- if(c->queueIsFull()) return false;
- }
- return true;
+AsyncWebSocketClient* AsyncWebSocket::client(uint32_t id) {
+ const auto iter = std::find_if(_clients.begin(), _clients.end(), [id](const AsyncWebSocketClient& c) { return c.id() == id && c.status() == WS_CONNECTED; });
+ if (iter == std::end(_clients))
+ return nullptr;
+
+ return &(*iter);
}
-bool AsyncWebSocket::availableForWrite(uint32_t id){
- for(const auto& c: _clients){
- if(c->queueIsFull() && (c->id() == id )) return false;
- }
- return true;
+void AsyncWebSocket::close(uint32_t id, uint16_t code, const char* message) {
+ if (AsyncWebSocketClient* c = client(id))
+ c->close(code, message);
}
-size_t AsyncWebSocket::count() const {
- return _clients.count_if([](AsyncWebSocketClient * c){
- return c->status() == WS_CONNECTED;
- });
+void AsyncWebSocket::closeAll(uint16_t code, const char* message) {
+ for (auto& c : _clients)
+ if (c.status() == WS_CONNECTED)
+ c.close(code, message);
}
-AsyncWebSocketClient * AsyncWebSocket::client(uint32_t id){
- for(const auto &c: _clients){
- if(c->id() == id && c->status() == WS_CONNECTED){
- return c;
- }
+void AsyncWebSocket::cleanupClients(uint16_t maxClients) {
+ if (count() > maxClients)
+ _clients.front().close();
+
+ for (auto iter = std::begin(_clients); iter != std::end(_clients);) {
+ if (iter->shouldBeDeleted())
+ iter = _clients.erase(iter);
+ else
+ iter++;
}
- return nullptr;
}
+bool AsyncWebSocket::ping(uint32_t id, const uint8_t* data, size_t len) {
+ AsyncWebSocketClient* c = client(id);
+ return c && c->ping(data, len);
+}
-void AsyncWebSocket::close(uint32_t id, uint16_t code, const char * message){
- AsyncWebSocketClient * c = client(id);
- if(c)
- c->close(code, message);
+AsyncWebSocket::SendStatus AsyncWebSocket::pingAll(const uint8_t* data, size_t len) {
+ size_t hit = 0;
+ size_t miss = 0;
+ for (auto& c : _clients)
+ if (c.status() == WS_CONNECTED && c.ping(data, len))
+ hit++;
+ else
+ miss++;
+ return hit == 0 ? DISCARDED : (miss == 0 ? ENQUEUED : PARTIALLY_ENQUEUED);
}
-void AsyncWebSocket::closeAll(uint16_t code, const char * message){
- for(const auto& c: _clients){
- if(c->status() == WS_CONNECTED)
- c->close(code, message);
- }
+bool AsyncWebSocket::text(uint32_t id, const uint8_t* message, size_t len) {
+ AsyncWebSocketClient* c = client(id);
+ return c && c->text(makeSharedBuffer(message, len));
+}
+bool AsyncWebSocket::text(uint32_t id, const char* message, size_t len) {
+ return text(id, (const uint8_t*)message, len);
+}
+bool AsyncWebSocket::text(uint32_t id, const char* message) {
+ return text(id, message, strlen(message));
+}
+bool AsyncWebSocket::text(uint32_t id, const String& message) {
+ return text(id, message.c_str(), message.length());
}
-void AsyncWebSocket::ping(uint32_t id, uint8_t *data, size_t len){
- AsyncWebSocketClient * c = client(id);
- if(c)
- c->ping(data, len);
+#ifdef ESP8266
+bool AsyncWebSocket::text(uint32_t id, const __FlashStringHelper* data) {
+ PGM_P p = reinterpret_cast(data);
+
+ size_t n = 0;
+ while (true) {
+ if (pgm_read_byte(p + n) == 0)
+ break;
+ n += 1;
+ }
+
+ char* message = (char*)malloc(n + 1);
+ bool enqueued = false;
+ if (message) {
+ memcpy_P(message, p, n);
+ message[n] = 0;
+ enqueued = text(id, message, n);
+ free(message);
+ }
+ return enqueued;
}
+#endif // ESP8266
-void AsyncWebSocket::pingAll(uint8_t *data, size_t len){
- for(const auto& c: _clients){
- if(c->status() == WS_CONNECTED)
- c->ping(data, len);
+bool AsyncWebSocket::text(uint32_t id, AsyncWebSocketMessageBuffer* buffer) {
+ bool enqueued = false;
+ if (buffer) {
+ enqueued = text(id, std::move(buffer->_buffer));
+ delete buffer;
}
+ return enqueued;
+}
+bool AsyncWebSocket::text(uint32_t id, AsyncWebSocketSharedBuffer buffer) {
+ AsyncWebSocketClient* c = client(id);
+ return c && c->text(buffer);
}
-void AsyncWebSocket::text(uint32_t id, const char * message, size_t len){
- AsyncWebSocketClient * c = client(id);
- if(c)
- c->text(message, len);
+AsyncWebSocket::SendStatus AsyncWebSocket::textAll(const uint8_t* message, size_t len) {
+ return textAll(makeSharedBuffer(message, len));
}
+AsyncWebSocket::SendStatus AsyncWebSocket::textAll(const char* message, size_t len) {
+ return textAll((const uint8_t*)message, len);
+}
+AsyncWebSocket::SendStatus AsyncWebSocket::textAll(const char* message) {
+ return textAll(message, strlen(message));
+}
+AsyncWebSocket::SendStatus AsyncWebSocket::textAll(const String& message) {
+ return textAll(message.c_str(), message.length());
+}
+#ifdef ESP8266
+AsyncWebSocket::SendStatus AsyncWebSocket::textAll(const __FlashStringHelper* data) {
+ PGM_P p = reinterpret_cast(data);
-void AsyncWebSocket::textAll(AsyncWebSocketMessageBuffer * buffer){
- if (!buffer) return;
- buffer->lock();
- for(const auto& c: _clients){
- if(c->status() == WS_CONNECTED){
- c->text(buffer);
- }
+ size_t n = 0;
+ while (1) {
+ if (pgm_read_byte(p + n) == 0)
+ break;
+ n += 1;
}
- buffer->unlock();
- _cleanBuffers();
-}
+ char* message = (char*)malloc(n + 1);
+ AsyncWebSocket::SendStatus status = DISCARDED;
+ if (message) {
+ memcpy_P(message, p, n);
+ message[n] = 0;
+ status = textAll(message, n);
+ free(message);
+ }
+ return status;
+}
+#endif // ESP8266
+AsyncWebSocket::SendStatus AsyncWebSocket::textAll(AsyncWebSocketMessageBuffer* buffer) {
+ AsyncWebSocket::SendStatus status = DISCARDED;
+ if (buffer) {
+ status = textAll(std::move(buffer->_buffer));
+ delete buffer;
+ }
+ return status;
+}
-void AsyncWebSocket::textAll(const char * message, size_t len){
- AsyncWebSocketMessageBuffer * WSBuffer = makeBuffer((uint8_t *)message, len);
- textAll(WSBuffer);
+AsyncWebSocket::SendStatus AsyncWebSocket::textAll(AsyncWebSocketSharedBuffer buffer) {
+ size_t hit = 0;
+ size_t miss = 0;
+ for (auto& c : _clients)
+ if (c.status() == WS_CONNECTED && c.text(buffer))
+ hit++;
+ else
+ miss++;
+ return hit == 0 ? DISCARDED : (miss == 0 ? ENQUEUED : PARTIALLY_ENQUEUED);
}
-void AsyncWebSocket::binary(uint32_t id, const char * message, size_t len){
- AsyncWebSocketClient * c = client(id);
- if(c)
- c->binary(message, len);
+bool AsyncWebSocket::binary(uint32_t id, const uint8_t* message, size_t len) {
+ AsyncWebSocketClient* c = client(id);
+ return c && c->binary(makeSharedBuffer(message, len));
+}
+bool AsyncWebSocket::binary(uint32_t id, const char* message, size_t len) {
+ return binary(id, (const uint8_t*)message, len);
+}
+bool AsyncWebSocket::binary(uint32_t id, const char* message) {
+ return binary(id, message, strlen(message));
+}
+bool AsyncWebSocket::binary(uint32_t id, const String& message) {
+ return binary(id, message.c_str(), message.length());
}
-void AsyncWebSocket::binaryAll(const char * message, size_t len){
- AsyncWebSocketMessageBuffer * buffer = makeBuffer((uint8_t *)message, len);
- binaryAll(buffer);
+#ifdef ESP8266
+bool AsyncWebSocket::binary(uint32_t id, const __FlashStringHelper* data, size_t len) {
+ PGM_P p = reinterpret_cast(data);
+ char* message = (char*)malloc(len);
+ bool enqueued = false;
+ if (message) {
+ memcpy_P(message, p, len);
+ enqueued = binary(id, message, len);
+ free(message);
+ }
+ return enqueued;
}
+#endif // ESP8266
-void AsyncWebSocket::binaryAll(AsyncWebSocketMessageBuffer * buffer)
-{
- if (!buffer) return;
- buffer->lock();
- for(const auto& c: _clients){
- if(c->status() == WS_CONNECTED)
- c->binary(buffer);
+bool AsyncWebSocket::binary(uint32_t id, AsyncWebSocketMessageBuffer* buffer) {
+ bool enqueued = false;
+ if (buffer) {
+ enqueued = binary(id, std::move(buffer->_buffer));
+ delete buffer;
}
- buffer->unlock();
- _cleanBuffers();
+ return enqueued;
+}
+bool AsyncWebSocket::binary(uint32_t id, AsyncWebSocketSharedBuffer buffer) {
+ AsyncWebSocketClient* c = client(id);
+ return c && c->binary(buffer);
}
-void AsyncWebSocket::message(uint32_t id, AsyncWebSocketMessage *message){
- AsyncWebSocketClient * c = client(id);
- if(c)
- c->message(message);
+AsyncWebSocket::SendStatus AsyncWebSocket::binaryAll(const uint8_t* message, size_t len) {
+ return binaryAll(makeSharedBuffer(message, len));
+}
+AsyncWebSocket::SendStatus AsyncWebSocket::binaryAll(const char* message, size_t len) {
+ return binaryAll((const uint8_t*)message, len);
+}
+AsyncWebSocket::SendStatus AsyncWebSocket::binaryAll(const char* message) {
+ return binaryAll(message, strlen(message));
+}
+AsyncWebSocket::SendStatus AsyncWebSocket::binaryAll(const String& message) {
+ return binaryAll(message.c_str(), message.length());
}
-void AsyncWebSocket::messageAll(AsyncWebSocketMultiMessage *message){
- for(const auto& c: _clients){
- if(c->status() == WS_CONNECTED)
- c->message(message);
+#ifdef ESP8266
+AsyncWebSocket::SendStatus AsyncWebSocket::binaryAll(const __FlashStringHelper* data, size_t len) {
+ PGM_P p = reinterpret_cast(data);
+ char* message = (char*)malloc(len);
+ AsyncWebSocket::SendStatus status = DISCARDED;
+ if (message) {
+ memcpy_P(message, p, len);
+ status = binaryAll(message, len);
+ free(message);
}
- _cleanBuffers();
+ return status;
}
+#endif // ESP8266
-size_t AsyncWebSocket::printf(uint32_t id, const char *format, ...){
- AsyncWebSocketClient * c = client(id);
- if(c){
+AsyncWebSocket::SendStatus AsyncWebSocket::binaryAll(AsyncWebSocketMessageBuffer* buffer) {
+ AsyncWebSocket::SendStatus status = DISCARDED;
+ if (buffer) {
+ status = binaryAll(std::move(buffer->_buffer));
+ delete buffer;
+ }
+ return status;
+}
+AsyncWebSocket::SendStatus AsyncWebSocket::binaryAll(AsyncWebSocketSharedBuffer buffer) {
+ size_t hit = 0;
+ size_t miss = 0;
+ for (auto& c : _clients)
+ if (c.status() == WS_CONNECTED && c.binary(buffer))
+ hit++;
+ else
+ miss++;
+ return hit == 0 ? DISCARDED : (miss == 0 ? ENQUEUED : PARTIALLY_ENQUEUED);
+}
+
+size_t AsyncWebSocket::printf(uint32_t id, const char* format, ...) {
+ AsyncWebSocketClient* c = client(id);
+ if (c) {
va_list arg;
va_start(arg, format);
size_t len = c->printf(format, arg);
@@ -1010,34 +1049,33 @@ size_t AsyncWebSocket::printf(uint32_t id, const char *format, ...){
return 0;
}
-size_t AsyncWebSocket::printfAll(const char *format, ...) {
+size_t AsyncWebSocket::printfAll(const char* format, ...) {
va_list arg;
- char* temp = new char[MAX_PRINTF_LEN];
- if(!temp){
- return 0;
- }
va_start(arg, format);
- size_t len = vsnprintf(temp, MAX_PRINTF_LEN, format, arg);
+ size_t len = vsnprintf(nullptr, 0, format, arg);
va_end(arg);
- delete[] temp;
-
- AsyncWebSocketMessageBuffer * buffer = makeBuffer(len);
- if (!buffer) {
+
+ if (len == 0)
+ return 0;
+
+ char* buffer = new char[len + 1];
+
+ if (!buffer)
return 0;
- }
va_start(arg, format);
- vsnprintf( (char *)buffer->get(), len + 1, format, arg);
+ len = vsnprintf(buffer, len + 1, format, arg);
va_end(arg);
- textAll(buffer);
- return len;
+ AsyncWebSocket::SendStatus status = textAll(buffer, len);
+ delete[] buffer;
+ return status == DISCARDED ? 0 : len;
}
-#ifndef ESP32
-size_t AsyncWebSocket::printf_P(uint32_t id, PGM_P formatP, ...){
- AsyncWebSocketClient * c = client(id);
- if(c != NULL){
+#ifdef ESP8266
+size_t AsyncWebSocket::printf_P(uint32_t id, PGM_P formatP, ...) {
+ AsyncWebSocketClient* c = client(id);
+ if (c != NULL) {
va_list arg;
va_start(arg, formatP);
size_t len = c->printf_P(formatP, arg);
@@ -1046,237 +1084,156 @@ size_t AsyncWebSocket::printf_P(uint32_t id, PGM_P formatP, ...){
}
return 0;
}
-#endif
size_t AsyncWebSocket::printfAll_P(PGM_P formatP, ...) {
va_list arg;
- char* temp = new char[MAX_PRINTF_LEN];
- if(!temp){
- return 0;
- }
va_start(arg, formatP);
- size_t len = vsnprintf_P(temp, MAX_PRINTF_LEN, formatP, arg);
+ size_t len = vsnprintf_P(nullptr, 0, formatP, arg);
va_end(arg);
- delete[] temp;
-
- AsyncWebSocketMessageBuffer * buffer = makeBuffer(len + 1);
- if (!buffer) {
+
+ if (len == 0)
return 0;
- }
- va_start(arg, formatP);
- vsnprintf_P((char *)buffer->get(), len + 1, formatP, arg);
- va_end(arg);
+ char* buffer = new char[len + 1];
- textAll(buffer);
- return len;
-}
+ if (!buffer)
+ return 0;
-void AsyncWebSocket::text(uint32_t id, const char * message){
- text(id, message, strlen(message));
-}
-void AsyncWebSocket::text(uint32_t id, uint8_t * message, size_t len){
- text(id, (const char *)message, len);
-}
-void AsyncWebSocket::text(uint32_t id, char * message){
- text(id, message, strlen(message));
-}
-void AsyncWebSocket::text(uint32_t id, const String &message){
- text(id, message.c_str(), message.length());
-}
-void AsyncWebSocket::text(uint32_t id, const __FlashStringHelper *message){
- AsyncWebSocketClient * c = client(id);
- if(c != NULL)
- c->text(message);
-}
-void AsyncWebSocket::textAll(const char * message){
- textAll(message, strlen(message));
-}
-void AsyncWebSocket::textAll(uint8_t * message, size_t len){
- textAll((const char *)message, len);
-}
-void AsyncWebSocket::textAll(char * message){
- textAll(message, strlen(message));
-}
-void AsyncWebSocket::textAll(const String &message){
- textAll(message.c_str(), message.length());
-}
-void AsyncWebSocket::textAll(const __FlashStringHelper *message){
- for(const auto& c: _clients){
- if(c->status() == WS_CONNECTED)
- c->text(message);
- }
-}
-void AsyncWebSocket::binary(uint32_t id, const char * message){
- binary(id, message, strlen(message));
-}
-void AsyncWebSocket::binary(uint32_t id, uint8_t * message, size_t len){
- binary(id, (const char *)message, len);
-}
-void AsyncWebSocket::binary(uint32_t id, char * message){
- binary(id, message, strlen(message));
-}
-void AsyncWebSocket::binary(uint32_t id, const String &message){
- binary(id, message.c_str(), message.length());
-}
-void AsyncWebSocket::binary(uint32_t id, const __FlashStringHelper *message, size_t len){
- AsyncWebSocketClient * c = client(id);
- if(c != NULL)
- c-> binary(message, len);
-}
-void AsyncWebSocket::binaryAll(const char * message){
- binaryAll(message, strlen(message));
-}
-void AsyncWebSocket::binaryAll(uint8_t * message, size_t len){
- binaryAll((const char *)message, len);
-}
-void AsyncWebSocket::binaryAll(char * message){
- binaryAll(message, strlen(message));
-}
-void AsyncWebSocket::binaryAll(const String &message){
- binaryAll(message.c_str(), message.length());
-}
-void AsyncWebSocket::binaryAll(const __FlashStringHelper *message, size_t len){
- for(const auto& c: _clients){
- if(c->status() == WS_CONNECTED)
- c-> binary(message, len);
- }
- }
-
-const char * WS_STR_CONNECTION = "Connection";
-const char * WS_STR_UPGRADE = "Upgrade";
-const char * WS_STR_ORIGIN = "Origin";
-const char * WS_STR_VERSION = "Sec-WebSocket-Version";
-const char * WS_STR_KEY = "Sec-WebSocket-Key";
-const char * WS_STR_PROTOCOL = "Sec-WebSocket-Protocol";
-const char * WS_STR_ACCEPT = "Sec-WebSocket-Accept";
-const char * WS_STR_UUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
-
-bool AsyncWebSocket::canHandle(AsyncWebServerRequest *request){
- if(!_enabled)
- return false;
-
- if(request->method() != HTTP_GET || !request->url().equals(_url) || !request->isExpectedRequestedConnType(RCT_WS))
- return false;
+ va_start(arg, formatP);
+ len = vsnprintf_P(buffer, len + 1, formatP, arg);
+ va_end(arg);
- request->addInterestingHeader(WS_STR_CONNECTION);
- request->addInterestingHeader(WS_STR_UPGRADE);
- request->addInterestingHeader(WS_STR_ORIGIN);
- request->addInterestingHeader(WS_STR_VERSION);
- request->addInterestingHeader(WS_STR_KEY);
- request->addInterestingHeader(WS_STR_PROTOCOL);
- return true;
+ AsyncWebSocket::SendStatus status = textAll(buffer, len);
+ delete[] buffer;
+ return status == DISCARDED ? 0 : len;
}
+#endif
-void AsyncWebSocket::handleRequest(AsyncWebServerRequest *request){
- if(!request->hasHeader(WS_STR_VERSION) || !request->hasHeader(WS_STR_KEY)){
+const char __WS_STR_CONNECTION[] PROGMEM = {"Connection"};
+const char __WS_STR_UPGRADE[] PROGMEM = {"Upgrade"};
+const char __WS_STR_ORIGIN[] PROGMEM = {"Origin"};
+const char __WS_STR_COOKIE[] PROGMEM = {"Cookie"};
+const char __WS_STR_VERSION[] PROGMEM = {"Sec-WebSocket-Version"};
+const char __WS_STR_KEY[] PROGMEM = {"Sec-WebSocket-Key"};
+const char __WS_STR_PROTOCOL[] PROGMEM = {"Sec-WebSocket-Protocol"};
+const char __WS_STR_ACCEPT[] PROGMEM = {"Sec-WebSocket-Accept"};
+const char __WS_STR_UUID[] PROGMEM = {"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"};
+
+#define WS_STR_UUID_LEN 36
+
+#define WS_STR_CONNECTION FPSTR(__WS_STR_CONNECTION)
+#define WS_STR_UPGRADE FPSTR(__WS_STR_UPGRADE)
+#define WS_STR_ORIGIN FPSTR(__WS_STR_ORIGIN)
+#define WS_STR_COOKIE FPSTR(__WS_STR_COOKIE)
+#define WS_STR_VERSION FPSTR(__WS_STR_VERSION)
+#define WS_STR_KEY FPSTR(__WS_STR_KEY)
+#define WS_STR_PROTOCOL FPSTR(__WS_STR_PROTOCOL)
+#define WS_STR_ACCEPT FPSTR(__WS_STR_ACCEPT)
+#define WS_STR_UUID FPSTR(__WS_STR_UUID)
+
+bool AsyncWebSocket::canHandle(AsyncWebServerRequest* request) const {
+ return _enabled && request->isWebSocketUpgrade() && request->url().equals(_url);
+}
+
+void AsyncWebSocket::handleRequest(AsyncWebServerRequest* request) {
+ if (!request->hasHeader(WS_STR_VERSION) || !request->hasHeader(WS_STR_KEY)) {
request->send(400);
return;
}
- if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str())){
- return request->requestAuthentication();
+ if (_handshakeHandler != nullptr) {
+ if (!_handshakeHandler(request)) {
+ request->send(401);
+ return;
+ }
}
- AsyncWebHeader* version = request->getHeader(WS_STR_VERSION);
- if(version->value().toInt() != 13){
- AsyncWebServerResponse *response = request->beginResponse(400);
- response->addHeader(WS_STR_VERSION,"13");
+ const AsyncWebHeader* version = request->getHeader(WS_STR_VERSION);
+ if (version->value().toInt() != 13) {
+ AsyncWebServerResponse* response = request->beginResponse(400);
+ response->addHeader(WS_STR_VERSION, T_13);
request->send(response);
return;
}
- AsyncWebHeader* key = request->getHeader(WS_STR_KEY);
- AsyncWebServerResponse *response = new AsyncWebSocketResponse(key->value(), this);
- if(request->hasHeader(WS_STR_PROTOCOL)){
- AsyncWebHeader* protocol = request->getHeader(WS_STR_PROTOCOL);
- //ToDo: check protocol
+ const AsyncWebHeader* key = request->getHeader(WS_STR_KEY);
+ AsyncWebServerResponse* response = new AsyncWebSocketResponse(key->value(), this);
+ if (request->hasHeader(WS_STR_PROTOCOL)) {
+ const AsyncWebHeader* protocol = request->getHeader(WS_STR_PROTOCOL);
+ // ToDo: check protocol
response->addHeader(WS_STR_PROTOCOL, protocol->value());
}
request->send(response);
}
-AsyncWebSocketMessageBuffer * AsyncWebSocket::makeBuffer(size_t size)
-{
- AsyncWebSocketMessageBuffer * buffer = new AsyncWebSocketMessageBuffer(size);
- if (buffer) {
- _buffers.add(buffer);
- }
- return buffer;
-}
-
-AsyncWebSocketMessageBuffer * AsyncWebSocket::makeBuffer(uint8_t * data, size_t size)
-{
- AsyncWebSocketMessageBuffer * buffer = new AsyncWebSocketMessageBuffer(data, size);
-
- if (buffer) {
- _buffers.add(buffer);
+AsyncWebSocketMessageBuffer* AsyncWebSocket::makeBuffer(size_t size) {
+ AsyncWebSocketMessageBuffer* buffer = new AsyncWebSocketMessageBuffer(size);
+ if (buffer->length() != size) {
+ delete buffer;
+ return nullptr;
+ } else {
+ return buffer;
}
-
- return buffer;
}
-void AsyncWebSocket::_cleanBuffers()
-{
- for(AsyncWebSocketMessageBuffer * c: _buffers){
- if(c && c->canDelete()){
- _buffers.remove(c);
- }
+AsyncWebSocketMessageBuffer* AsyncWebSocket::makeBuffer(const uint8_t* data, size_t size) {
+ AsyncWebSocketMessageBuffer* buffer = new AsyncWebSocketMessageBuffer(data, size);
+ if (buffer->length() != size) {
+ delete buffer;
+ return nullptr;
+ } else {
+ return buffer;
}
}
-
/*
* Response to Web Socket request - sends the authorization and detaches the TCP Client from the web server
* Authentication code from https://github.com/Links2004/arduinoWebSockets/blob/master/src/WebSockets.cpp#L480
*/
-AsyncWebSocketResponse::AsyncWebSocketResponse(const String& key, AsyncWebSocket *server){
+AsyncWebSocketResponse::AsyncWebSocketResponse(const String& key, AsyncWebSocket* server) {
_server = server;
_code = 101;
_sendContentLength = false;
- uint8_t * hash = (uint8_t*)malloc(20);
- if(hash == NULL){
- _state = RESPONSE_FAILED;
- return;
- }
- char * buffer = (char *) malloc(33);
- if(buffer == NULL){
- free(hash);
- _state = RESPONSE_FAILED;
- return;
- }
-#ifdef ESP8266
+ uint8_t hash[20];
+ char buffer[33];
+
+#if defined(ESP8266) || defined(TARGET_RP2040)
sha1(key + WS_STR_UUID, hash);
#else
- (String&)key += WS_STR_UUID;
- SHA1_CTX ctx;
- SHA1Init(&ctx);
- SHA1Update(&ctx, (const unsigned char*)key.c_str(), key.length());
- SHA1Final(hash, &ctx);
+ String k;
+ k.reserve(key.length() + WS_STR_UUID_LEN);
+ k.concat(key);
+ k.concat(WS_STR_UUID);
+ SHA1Builder sha1;
+ sha1.begin();
+ sha1.add((const uint8_t*)k.c_str(), k.length());
+ sha1.calculate();
+ sha1.getBytes(hash);
#endif
base64_encodestate _state;
base64_init_encodestate(&_state);
- int len = base64_encode_block((const char *) hash, 20, buffer, &_state);
+ int len = base64_encode_block((const char*)hash, 20, buffer, &_state);
len = base64_encode_blockend((buffer + len), &_state);
addHeader(WS_STR_CONNECTION, WS_STR_UPGRADE);
- addHeader(WS_STR_UPGRADE, "websocket");
- addHeader(WS_STR_ACCEPT,buffer);
- free(buffer);
- free(hash);
+ addHeader(WS_STR_UPGRADE, T_WS);
+ addHeader(WS_STR_ACCEPT, buffer);
}
-void AsyncWebSocketResponse::_respond(AsyncWebServerRequest *request){
- if(_state == RESPONSE_FAILED){
+void AsyncWebSocketResponse::_respond(AsyncWebServerRequest* request) {
+ if (_state == RESPONSE_FAILED) {
request->client()->close(true);
return;
}
- String out = _assembleHead(request->version());
+ String out;
+ _assembleHead(out, request->version());
request->client()->write(out.c_str(), _headLength);
_state = RESPONSE_WAIT_ACK;
}
-size_t AsyncWebSocketResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){
- if(len){
- new AsyncWebSocketClient(request, _server);
- }
+size_t AsyncWebSocketResponse::_ack(AsyncWebServerRequest* request, size_t len, uint32_t time) {
+ (void)time;
+
+ if (len)
+ _server->_newClient(request);
+
return 0;
}
diff --git a/src/AsyncWebSocket.h b/src/AsyncWebSocket.h
index 2a47a0d83..671c8664f 100644
--- a/src/AsyncWebSocket.h
+++ b/src/AsyncWebSocket.h
@@ -23,18 +23,44 @@
#include
#ifdef ESP32
-#include
-#define WS_MAX_QUEUED_MESSAGES 32
-#else
-#include
-#define WS_MAX_QUEUED_MESSAGES 8
+ #include
+ #include
+ #ifndef WS_MAX_QUEUED_MESSAGES
+ #define WS_MAX_QUEUED_MESSAGES 32
+ #endif
+#elif defined(ESP8266)
+ #include
+ #ifndef WS_MAX_QUEUED_MESSAGES
+ #define WS_MAX_QUEUED_MESSAGES 8
+ #endif
+#elif defined(TARGET_RP2040)
+ #include
+ #ifndef WS_MAX_QUEUED_MESSAGES
+ #define WS_MAX_QUEUED_MESSAGES 32
+ #endif
#endif
+
#include
+#include
+
#ifdef ESP8266
-#include
+ #include
+ #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
+#endif
+
+using AsyncWebSocketSharedBuffer = std::shared_ptr>;
+
class AsyncWebSocket;
class AsyncWebSocketResponse;
class AsyncWebSocketClient;
@@ -64,94 +90,74 @@ typedef struct {
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_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;
+
private:
- uint8_t * _data;
- size_t _len;
- bool _lock;
- uint32_t _count;
+ AsyncWebSocketSharedBuffer _buffer;
public:
- AsyncWebSocketMessageBuffer();
- AsyncWebSocketMessageBuffer(size_t size);
- AsyncWebSocketMessageBuffer(uint8_t * data, size_t size);
- AsyncWebSocketMessageBuffer(const AsyncWebSocketMessageBuffer &);
- AsyncWebSocketMessageBuffer(AsyncWebSocketMessageBuffer &&);
- ~AsyncWebSocketMessageBuffer();
- void operator ++(int i) { _count++; }
- void operator --(int i) { if (_count > 0) { _count--; } ; }
+ AsyncWebSocketMessageBuffer() {}
+ explicit AsyncWebSocketMessageBuffer(size_t size);
+ AsyncWebSocketMessageBuffer(const uint8_t* data, size_t size);
+ //~AsyncWebSocketMessageBuffer();
bool reserve(size_t size);
- void lock() { _lock = true; }
- void unlock() { _lock = false; }
- uint8_t * get() { return _data; }
- size_t length() { return _len; }
- uint32_t count() { return _count; }
- bool canDelete() { return (!_count && !_lock); }
-
- friend AsyncWebSocket;
-
+ uint8_t* get() { return _buffer->data(); }
+ size_t length() const { return _buffer->size(); }
};
class AsyncWebSocketMessage {
- protected:
- uint8_t _opcode;
- bool _mask;
- AwsMessageStatus _status;
+ 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():_opcode(WS_TEXT),_mask(false),_status(WS_MSG_ERROR){}
- virtual ~AsyncWebSocketMessage(){}
- virtual void ack(size_t len __attribute__((unused)), uint32_t time __attribute__((unused))){}
- virtual size_t send(AsyncClient *client __attribute__((unused))){ return 0; }
- virtual bool finished(){ return _status != WS_MSG_SENDING; }
- virtual bool betweenFrames() const { return false; }
-};
+ AsyncWebSocketMessage(AsyncWebSocketSharedBuffer buffer, uint8_t opcode = WS_TEXT, bool mask = false);
-class AsyncWebSocketBasicMessage: public AsyncWebSocketMessage {
- private:
- size_t _len;
- size_t _sent;
- size_t _ack;
- size_t _acked;
- uint8_t * _data;
-public:
- AsyncWebSocketBasicMessage(const char * data, size_t len, uint8_t opcode=WS_TEXT, bool mask=false);
- AsyncWebSocketBasicMessage(uint8_t opcode=WS_TEXT, bool mask=false);
- virtual ~AsyncWebSocketBasicMessage() override;
- virtual bool betweenFrames() const override { return _acked == _ack; }
- virtual void ack(size_t len, uint32_t time) override ;
- virtual size_t send(AsyncClient *client) override ;
-};
+ bool finished() const { return _status != WS_MSG_SENDING; }
+ bool betweenFrames() const { return _acked == _ack; }
-class AsyncWebSocketMultiMessage: public AsyncWebSocketMessage {
- private:
- uint8_t * _data;
- size_t _len;
- size_t _sent;
- size_t _ack;
- size_t _acked;
- AsyncWebSocketMessageBuffer * _WSbuffer;
-public:
- AsyncWebSocketMultiMessage(AsyncWebSocketMessageBuffer * buffer, uint8_t opcode=WS_TEXT, bool mask=false);
- virtual ~AsyncWebSocketMultiMessage() override;
- virtual bool betweenFrames() const override { return _acked == _ack; }
- virtual void ack(size_t len, uint32_t time) override ;
- virtual size_t send(AsyncClient *client) override ;
+ void ack(size_t len, uint32_t time);
+ size_t send(AsyncClient* client);
};
class AsyncWebSocketClient {
private:
- AsyncClient *_client;
- AsyncWebSocket *_server;
+ AsyncClient* _client;
+ AsyncWebSocket* _server;
uint32_t _clientId;
AwsClientStatus _status;
-
- LinkedList _controlQueue;
- LinkedList _messageQueue;
+#ifdef ESP32
+ mutable std::mutex _lock;
+#endif
+ std::deque _controlQueue;
+ std::deque _messageQueue;
+ bool closeWhenFull = true;
uint8_t _pstate;
AwsFrameInfo _pinfo;
@@ -159,174 +165,213 @@ class AsyncWebSocketClient {
uint32_t _lastMessageTime;
uint32_t _keepAlivePeriod;
- void _queueMessage(AsyncWebSocketMessage *dataMessage);
- void _queueControl(AsyncWebSocketControl *controlMessage);
+ 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;
+ void* _tempObject;
- AsyncWebSocketClient(AsyncWebServerRequest *request, AsyncWebSocket *server);
+ AsyncWebSocketClient(AsyncWebServerRequest* request, AsyncWebSocket* server);
~AsyncWebSocketClient();
- //client id increments for the given server
- uint32_t id(){ return _clientId; }
- AwsClientStatus status(){ return _status; }
- AsyncClient* client(){ return _client; }
- AsyncWebSocket *server(){ return _server; }
- AwsFrameInfo const &pinfo() const { return _pinfo; }
-
- IPAddress remoteIP();
- uint16_t remotePort();
-
- //control frames
- void close(uint16_t code=0, const char * message=NULL);
- void ping(uint8_t *data=NULL, size_t len=0);
-
- //set auto-ping period in seconds. disabled if zero (default)
- void keepAlivePeriod(uint16_t seconds){
+ // 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; }
+
+ IPAddress remoteIP() const;
+ uint16_t remotePort() const;
+
+ 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);
+
+ // set auto-ping period in seconds. disabled if zero (default)
+ void keepAlivePeriod(uint16_t seconds) {
_keepAlivePeriod = seconds * 1000;
}
- uint16_t keepAlivePeriod(){
+ uint16_t keepAlivePeriod() {
return (uint16_t)(_keepAlivePeriod / 1000);
}
- //data packets
- void message(AsyncWebSocketMessage *message){ _queueMessage(message); }
- bool queueIsFull();
+ // 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)));
-#ifndef ESP32
- size_t printf_P(PGM_P formatP, ...) __attribute__ ((format (printf, 2, 3)));
-#endif
- void text(const char * message, size_t len);
- void text(const char * message);
- void text(uint8_t * message, size_t len);
- void text(char * message);
- void text(const String &message);
- void text(const __FlashStringHelper *data);
- void text(AsyncWebSocketMessageBuffer *buffer);
-
- void binary(const char * message, size_t len);
- void binary(const char * message);
- void binary(uint8_t * message, size_t len);
- void binary(char * message);
- void binary(const String &message);
- void binary(const __FlashStringHelper *data, size_t len);
- void binary(AsyncWebSocketMessageBuffer *buffer);
-
- bool canSend() { return _messageQueue.length() < WS_MAX_QUEUED_MESSAGES; }
-
- //system callbacks (do not call)
+ 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 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;
+
+ // 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);
+ 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);
+#endif
};
-typedef std::function AwsEventHandler;
+using AwsHandshakeHandler = std::function;
+using AwsEventHandler = std::function;
-//WebServer Handler implementation that plays the role of a socket server
-class AsyncWebSocket: public AsyncWebHandler {
+// WebServer Handler implementation that plays the role of a socket server
+class AsyncWebSocket : public AsyncWebHandler {
private:
String _url;
- LinkedList _clients;
+ std::list _clients;
uint32_t _cNextId;
- AwsEventHandler _eventHandler;
+ AwsEventHandler _eventHandler{nullptr};
+ AwsHandshakeHandler _handshakeHandler;
bool _enabled;
+#ifdef ESP32
+ mutable std::mutex _lock;
+#endif
+
public:
- AsyncWebSocket(const String& url);
- ~AsyncWebSocket();
- const char * url() const { return _url.c_str(); }
- void enable(bool e){ _enabled = e; }
+ 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);
size_t count() const;
- AsyncWebSocketClient * client(uint32_t id);
- bool hasClient(uint32_t id){ return client(id) != NULL; }
-
- void close(uint32_t id, uint16_t code=0, const char * message=NULL);
- void closeAll(uint16_t code=0, const char * message=NULL);
-
- void ping(uint32_t id, uint8_t *data=NULL, size_t len=0);
- void pingAll(uint8_t *data=NULL, size_t len=0); // done
-
- void text(uint32_t id, const char * message, size_t len);
- void text(uint32_t id, const char * message);
- void text(uint32_t id, uint8_t * message, size_t len);
- void text(uint32_t id, char * message);
- void text(uint32_t id, const String &message);
- void text(uint32_t id, const __FlashStringHelper *message);
-
- void textAll(const char * message, size_t len);
- void textAll(const char * message);
- void textAll(uint8_t * message, size_t len);
- void textAll(char * message);
- void textAll(const String &message);
- void textAll(const __FlashStringHelper *message); // need to convert
- void textAll(AsyncWebSocketMessageBuffer * buffer);
-
- void binary(uint32_t id, const char * message, size_t len);
- void binary(uint32_t id, const char * message);
- void binary(uint32_t id, uint8_t * message, size_t len);
- void binary(uint32_t id, char * message);
- void binary(uint32_t id, const String &message);
- void binary(uint32_t id, const __FlashStringHelper *message, size_t len);
-
- void binaryAll(const char * message, size_t len);
- void binaryAll(const char * message);
- void binaryAll(uint8_t * message, size_t len);
- void binaryAll(char * message);
- void binaryAll(const String &message);
- void binaryAll(const __FlashStringHelper *message, size_t len);
- void binaryAll(AsyncWebSocketMessageBuffer * buffer);
-
- void message(uint32_t id, AsyncWebSocketMessage *message);
- void messageAll(AsyncWebSocketMultiMessage *message);
-
- 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)));
-#ifndef ESP32
- size_t printf_P(uint32_t id, PGM_P formatP, ...) __attribute__ ((format (printf, 3, 4)));
+ 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);
+
+ 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);
+
+ 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);
+
+ 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)));
+
+#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)));
#endif
- size_t printfAll_P(PGM_P formatP, ...) __attribute__ ((format (printf, 2, 3)));
- //event listener
- void onEvent(AwsEventHandler handler){
- _eventHandler = handler;
- }
+ void onEvent(AwsEventHandler handler) { _eventHandler = handler; }
+ void handleHandshake(AwsHandshakeHandler handler) { _handshakeHandler = handler; }
- //system callbacks (do not call)
- uint32_t _getNextId(){ return _cNextId++; }
- void _addClient(AsyncWebSocketClient * client);
- void _handleDisconnect(AsyncWebSocketClient * client);
- void _handleEvent(AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len);
- virtual bool canHandle(AsyncWebServerRequest *request) override final;
- virtual 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(uint8_t * data, size_t size);
- LinkedList _buffers;
- void _cleanBuffers();
+ std::list& getClients() { return _clients; }
};
-//WebServer response to authenticate the socket and detach the tcp client from the web server request
-class AsyncWebSocketResponse: public AsyncWebServerResponse {
+// 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;
+ AsyncWebSocket* _server;
+
public:
- AsyncWebSocketResponse(const String& key, AsyncWebSocket *server);
- void _respond(AsyncWebServerRequest *request);
- size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time);
+ 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_ */
diff --git a/src/BackPort_SHA1Builder.cpp b/src/BackPort_SHA1Builder.cpp
new file mode 100644
index 000000000..08ba32ca2
--- /dev/null
+++ b/src/BackPort_SHA1Builder.cpp
@@ -0,0 +1,284 @@
+/*
+ * FIPS-180-1 compliant SHA-1 implementation
+ *
+ * Copyright (C) 2006-2015, ARM Limited, All Rights Reserved
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * This file is part of mbed TLS (https://tls.mbed.org)
+ * Modified for esp32 by Lucas Saavedra Vaz on 11 Jan 2024
+ */
+
+#include
+#if ESP_IDF_VERSION_MAJOR < 5
+
+#include "BackPort_SHA1Builder.h"
+
+// 32-bit integer manipulation macros (big endian)
+
+#ifndef GET_UINT32_BE
+#define GET_UINT32_BE(n, b, i) \
+ { (n) = ((uint32_t)(b)[(i)] << 24) | ((uint32_t)(b)[(i) + 1] << 16) | ((uint32_t)(b)[(i) + 2] << 8) | ((uint32_t)(b)[(i) + 3]); }
+#endif
+
+#ifndef PUT_UINT32_BE
+#define PUT_UINT32_BE(n, b, i) \
+ { \
+ (b)[(i)] = (uint8_t)((n) >> 24); \
+ (b)[(i) + 1] = (uint8_t)((n) >> 16); \
+ (b)[(i) + 2] = (uint8_t)((n) >> 8); \
+ (b)[(i) + 3] = (uint8_t)((n)); \
+ }
+#endif
+
+// Constants
+
+static const uint8_t sha1_padding[64] = {0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+
+// Private methods
+
+void SHA1Builder::process(const uint8_t *data) {
+ uint32_t temp, W[16], A, B, C, D, E;
+
+ GET_UINT32_BE(W[0], data, 0);
+ GET_UINT32_BE(W[1], data, 4);
+ GET_UINT32_BE(W[2], data, 8);
+ GET_UINT32_BE(W[3], data, 12);
+ GET_UINT32_BE(W[4], data, 16);
+ GET_UINT32_BE(W[5], data, 20);
+ GET_UINT32_BE(W[6], data, 24);
+ GET_UINT32_BE(W[7], data, 28);
+ GET_UINT32_BE(W[8], data, 32);
+ GET_UINT32_BE(W[9], data, 36);
+ GET_UINT32_BE(W[10], data, 40);
+ GET_UINT32_BE(W[11], data, 44);
+ GET_UINT32_BE(W[12], data, 48);
+ GET_UINT32_BE(W[13], data, 52);
+ GET_UINT32_BE(W[14], data, 56);
+ GET_UINT32_BE(W[15], data, 60);
+
+#define sha1_S(x, n) ((x << n) | ((x & 0xFFFFFFFF) >> (32 - n)))
+
+#define sha1_R(t) (temp = W[(t - 3) & 0x0F] ^ W[(t - 8) & 0x0F] ^ W[(t - 14) & 0x0F] ^ W[t & 0x0F], (W[t & 0x0F] = sha1_S(temp, 1)))
+
+#define sha1_P(a, b, c, d, e, x) \
+ { \
+ e += sha1_S(a, 5) + sha1_F(b, c, d) + sha1_K + x; \
+ b = sha1_S(b, 30); \
+ }
+
+ A = state[0];
+ B = state[1];
+ C = state[2];
+ D = state[3];
+ E = state[4];
+
+#define sha1_F(x, y, z) (z ^ (x & (y ^ z)))
+#define sha1_K 0x5A827999
+
+ sha1_P(A, B, C, D, E, W[0]);
+ sha1_P(E, A, B, C, D, W[1]);
+ sha1_P(D, E, A, B, C, W[2]);
+ sha1_P(C, D, E, A, B, W[3]);
+ sha1_P(B, C, D, E, A, W[4]);
+ sha1_P(A, B, C, D, E, W[5]);
+ sha1_P(E, A, B, C, D, W[6]);
+ sha1_P(D, E, A, B, C, W[7]);
+ sha1_P(C, D, E, A, B, W[8]);
+ sha1_P(B, C, D, E, A, W[9]);
+ sha1_P(A, B, C, D, E, W[10]);
+ sha1_P(E, A, B, C, D, W[11]);
+ sha1_P(D, E, A, B, C, W[12]);
+ sha1_P(C, D, E, A, B, W[13]);
+ sha1_P(B, C, D, E, A, W[14]);
+ sha1_P(A, B, C, D, E, W[15]);
+ sha1_P(E, A, B, C, D, sha1_R(16));
+ sha1_P(D, E, A, B, C, sha1_R(17));
+ sha1_P(C, D, E, A, B, sha1_R(18));
+ sha1_P(B, C, D, E, A, sha1_R(19));
+
+#undef sha1_K
+#undef sha1_F
+
+#define sha1_F(x, y, z) (x ^ y ^ z)
+#define sha1_K 0x6ED9EBA1
+
+ sha1_P(A, B, C, D, E, sha1_R(20));
+ sha1_P(E, A, B, C, D, sha1_R(21));
+ sha1_P(D, E, A, B, C, sha1_R(22));
+ sha1_P(C, D, E, A, B, sha1_R(23));
+ sha1_P(B, C, D, E, A, sha1_R(24));
+ sha1_P(A, B, C, D, E, sha1_R(25));
+ sha1_P(E, A, B, C, D, sha1_R(26));
+ sha1_P(D, E, A, B, C, sha1_R(27));
+ sha1_P(C, D, E, A, B, sha1_R(28));
+ sha1_P(B, C, D, E, A, sha1_R(29));
+ sha1_P(A, B, C, D, E, sha1_R(30));
+ sha1_P(E, A, B, C, D, sha1_R(31));
+ sha1_P(D, E, A, B, C, sha1_R(32));
+ sha1_P(C, D, E, A, B, sha1_R(33));
+ sha1_P(B, C, D, E, A, sha1_R(34));
+ sha1_P(A, B, C, D, E, sha1_R(35));
+ sha1_P(E, A, B, C, D, sha1_R(36));
+ sha1_P(D, E, A, B, C, sha1_R(37));
+ sha1_P(C, D, E, A, B, sha1_R(38));
+ sha1_P(B, C, D, E, A, sha1_R(39));
+
+#undef sha1_K
+#undef sha1_F
+
+#define sha1_F(x, y, z) ((x & y) | (z & (x | y)))
+#define sha1_K 0x8F1BBCDC
+
+ sha1_P(A, B, C, D, E, sha1_R(40));
+ sha1_P(E, A, B, C, D, sha1_R(41));
+ sha1_P(D, E, A, B, C, sha1_R(42));
+ sha1_P(C, D, E, A, B, sha1_R(43));
+ sha1_P(B, C, D, E, A, sha1_R(44));
+ sha1_P(A, B, C, D, E, sha1_R(45));
+ sha1_P(E, A, B, C, D, sha1_R(46));
+ sha1_P(D, E, A, B, C, sha1_R(47));
+ sha1_P(C, D, E, A, B, sha1_R(48));
+ sha1_P(B, C, D, E, A, sha1_R(49));
+ sha1_P(A, B, C, D, E, sha1_R(50));
+ sha1_P(E, A, B, C, D, sha1_R(51));
+ sha1_P(D, E, A, B, C, sha1_R(52));
+ sha1_P(C, D, E, A, B, sha1_R(53));
+ sha1_P(B, C, D, E, A, sha1_R(54));
+ sha1_P(A, B, C, D, E, sha1_R(55));
+ sha1_P(E, A, B, C, D, sha1_R(56));
+ sha1_P(D, E, A, B, C, sha1_R(57));
+ sha1_P(C, D, E, A, B, sha1_R(58));
+ sha1_P(B, C, D, E, A, sha1_R(59));
+
+#undef sha1_K
+#undef sha1_F
+
+#define sha1_F(x, y, z) (x ^ y ^ z)
+#define sha1_K 0xCA62C1D6
+
+ sha1_P(A, B, C, D, E, sha1_R(60));
+ sha1_P(E, A, B, C, D, sha1_R(61));
+ sha1_P(D, E, A, B, C, sha1_R(62));
+ sha1_P(C, D, E, A, B, sha1_R(63));
+ sha1_P(B, C, D, E, A, sha1_R(64));
+ sha1_P(A, B, C, D, E, sha1_R(65));
+ sha1_P(E, A, B, C, D, sha1_R(66));
+ sha1_P(D, E, A, B, C, sha1_R(67));
+ sha1_P(C, D, E, A, B, sha1_R(68));
+ sha1_P(B, C, D, E, A, sha1_R(69));
+ sha1_P(A, B, C, D, E, sha1_R(70));
+ sha1_P(E, A, B, C, D, sha1_R(71));
+ sha1_P(D, E, A, B, C, sha1_R(72));
+ sha1_P(C, D, E, A, B, sha1_R(73));
+ sha1_P(B, C, D, E, A, sha1_R(74));
+ sha1_P(A, B, C, D, E, sha1_R(75));
+ sha1_P(E, A, B, C, D, sha1_R(76));
+ sha1_P(D, E, A, B, C, sha1_R(77));
+ sha1_P(C, D, E, A, B, sha1_R(78));
+ sha1_P(B, C, D, E, A, sha1_R(79));
+
+#undef sha1_K
+#undef sha1_F
+
+ state[0] += A;
+ state[1] += B;
+ state[2] += C;
+ state[3] += D;
+ state[4] += E;
+}
+
+// Public methods
+
+void SHA1Builder::begin() {
+ total[0] = 0;
+ total[1] = 0;
+
+ state[0] = 0x67452301;
+ state[1] = 0xEFCDAB89;
+ state[2] = 0x98BADCFE;
+ state[3] = 0x10325476;
+ state[4] = 0xC3D2E1F0;
+
+ memset(buffer, 0x00, sizeof(buffer));
+ memset(hash, 0x00, sizeof(hash));
+}
+
+void SHA1Builder::add(const uint8_t *data, size_t len) {
+ size_t fill;
+ uint32_t left;
+
+ if (len == 0) {
+ return;
+ }
+
+ left = total[0] & 0x3F;
+ fill = 64 - left;
+
+ total[0] += (uint32_t)len;
+ total[0] &= 0xFFFFFFFF;
+
+ if (total[0] < (uint32_t)len) {
+ total[1]++;
+ }
+
+ if (left && len >= fill) {
+ memcpy((void *)(buffer + left), data, fill);
+ process(buffer);
+ data += fill;
+ len -= fill;
+ left = 0;
+ }
+
+ while (len >= 64) {
+ process(data);
+ data += 64;
+ len -= 64;
+ }
+
+ if (len > 0) {
+ memcpy((void *)(buffer + left), data, len);
+ }
+}
+
+void SHA1Builder::calculate(void) {
+ uint32_t last, padn;
+ uint32_t high, low;
+ uint8_t msglen[8];
+
+ high = (total[0] >> 29) | (total[1] << 3);
+ low = (total[0] << 3);
+
+ PUT_UINT32_BE(high, msglen, 0);
+ PUT_UINT32_BE(low, msglen, 4);
+
+ last = total[0] & 0x3F;
+ padn = (last < 56) ? (56 - last) : (120 - last);
+
+ add((uint8_t *)sha1_padding, padn);
+ add(msglen, 8);
+
+ PUT_UINT32_BE(state[0], hash, 0);
+ PUT_UINT32_BE(state[1], hash, 4);
+ PUT_UINT32_BE(state[2], hash, 8);
+ PUT_UINT32_BE(state[3], hash, 12);
+ PUT_UINT32_BE(state[4], hash, 16);
+}
+
+void SHA1Builder::getBytes(uint8_t *output) {
+ memcpy(output, hash, SHA1_HASH_SIZE);
+}
+
+#endif // ESP_IDF_VERSION_MAJOR < 5
diff --git a/src/BackPort_SHA1Builder.h b/src/BackPort_SHA1Builder.h
new file mode 100644
index 000000000..8f518259b
--- /dev/null
+++ b/src/BackPort_SHA1Builder.h
@@ -0,0 +1,44 @@
+// Copyright 2024 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include
+#if ESP_IDF_VERSION_MAJOR < 5
+
+#ifndef SHA1Builder_h
+#define SHA1Builder_h
+
+#include
+#include
+
+#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 */
+
+ 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);
+};
+
+#endif // SHA1Builder_h
+
+#endif // ESP_IDF_VERSION_MAJOR < 5
diff --git a/src/ChunkPrint.cpp b/src/ChunkPrint.cpp
new file mode 100644
index 000000000..8c9717a56
--- /dev/null
+++ b/src/ChunkPrint.cpp
@@ -0,0 +1,16 @@
+#include
+
+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) {
+ _to_skip--;
+ return 1;
+ } else if (_to_write > 0) {
+ _to_write--;
+ _destination[_pos++] = c;
+ return 1;
+ }
+ return 0;
+}
\ No newline at end of file
diff --git a/src/ChunkPrint.h b/src/ChunkPrint.h
new file mode 100644
index 000000000..47988e17c
--- /dev/null
+++ b/src/ChunkPrint.h
@@ -0,0 +1,18 @@
+#ifndef CHUNKPRINT_H
+#define CHUNKPRINT_H
+
+#include
+
+class ChunkPrint : public Print {
+ 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); }
+};
+#endif
diff --git a/src/ESPAsyncWebServer.h b/src/ESPAsyncWebServer.h
index cfe1fa73d..00aed07d0 100644
--- a/src/ESPAsyncWebServer.h
+++ b/src/ESPAsyncWebServer.h
@@ -23,22 +23,42 @@
#include "Arduino.h"
-#include
#include "FS.h"
-
-#include "StringArray.h"
+#include
+#include
+#include
+#include
+#include
+#include
#ifdef ESP32
-#include
-#include
+ #include
+ #include
#elif defined(ESP8266)
-#include
-#include