+

+ESPAsyncWebServer

+ +

Async HTTP and WebSocket Server for ESP8266 and ESP32/ESP31B Arduino

+ +

Requires ESPAsyncTCP to work

+ +

+Why should you care

+ + + +

+Important things to remember

+ + + +

+Principles of operation

+ +

+The Async Web server

+ + + +

+Request Life Cycle

+ + + +

+Handlers and how do they work

+ + + +

+Responses and how do they work

+ + + +

+Request Variables

+ +

+Common Variables

+ +
request->version();       // uint8_t: 0 = HTTP/1.0, 1 = HTTP/1.1
+request->method();        // enum:    HTTP_GET, HTTP_POST, HTTP_DELETE, HTTP_PUT, HTTP_PATCH, HTTP_HEAD, HTTP_OPTIONS
+request->url();           // String:  URL of the request (not including host, port or GET parameters) 
+request->host();          // String:  The requested host (can be used for virtual hosting)
+request->contentType();   // String:  ContentType of the request (not avaiable in Handler::canHandle)
+request->contentLength(); // size_t:  ContentLength of the request (not avaiable in Handler::canHandle)
+request->multipart();     // bool:    True if the request has content type "multipart"
+ +

+Headers

+ +
//List all collected headers
+int headers = request->headers();
+int i;
+for(i=0;i<headers;i++){
+  AsyncWebHeader* h = request->getHeader(i);
+  Serial.printf("HEADER[%s]: %s\n", h->name().c_str(), h->value().c_str());
+}
+
+//get specific header by name
+if(request->hasHeader("MyHeader")){
+  AsyncWebHeader* h = request->getHeader("MyHeader");
+  Serial.printf("MyHeader: %s\n", h->value().c_str());
+}
+ +

+GET, POST and FILE parameters

+ +
//List all parameters
+int params = request->params();
+for(int i=0;i<params;i++){
+  AsyncWebParameter* p = request->getParam(i);
+  if(p->isFile()){ //p->isPost() is also true
+    Serial.printf("FILE[%s]: %s, size: %u\n", p->name().c_str(), p->value().c_str(), p->size());
+  } else if(p->isPost()){
+    Serial.printf("POST[%s]: %s\n", p->name().c_str(), p->value().c_str());
+  } else {
+    Serial.printf("GET[%s]: %s\n", p->name().c_str(), p->value().c_str());
+  }
+}
+
+//Check if GET parameter exists
+bool exists = request->hasParam("download");
+AsyncWebParameter* p = request->getParam("download");
+
+//Check if POST (but not File) parameter exists
+bool exists = request->hasParam("download", true);
+AsyncWebParameter* p = request->getParam("download", true);
+
+//Check if FILE was uploaded
+bool exists = request->hasParam("download", true, true);
+AsyncWebParameter* p = request->getParam("download", true, true);
+ +

+FILE Upload handling

+ +
void handleUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final){
+  if(!index){
+    Serial.printf("UploadStart: %s\n", filename.c_str());
+  }
+  for(size_t i=0; i<len; i++){
+    Serial.write(data[i]);
+  }
+  if(final){
+    Serial.printf("UploadEnd: %s, %u B\n", filename.c_str(), index+len);
+  }
+}
+ +

+Body data handling

+ +
void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total){
+  if(!index){
+    Serial.printf("BodyStart: %u B\n", total);
+  }
+  for(size_t i=0; i<len; i++){
+    Serial.write(data[i]);
+  }
+  if(index + len == total){
+    Serial.printf("BodyEnd: %u B\n", total);
+  }
+}
+ +

+Responses

+ +

+Basic response with HTTP Code

+ +
request->send(404); //Sends 404 File Not Found
+ +

+Basic response with HTTP Code and extra headers

+ +
AsyncWebServerResponse *response = request->beginResponse(404); //Sends 404 File Not Found
+response->addHeader("Server","ESP Async Web Server");
+request->send(response);
+ +

+Basic response with string content

+ +
request->send(200, "text/plain", "Hello World!");
+ +

+Basic response with string content and extra headers

+ +
AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", "Hello World!");
+response->addHeader("Server","ESP Async Web Server");
+request->send(response);
+ +

+Respond with content coming from a Stream

+ +
//read 12 bytes from Serial and send them as Content Type text/plain
+request->send(Serial, "text/plain", 12);
+ +

+Respond with content coming from a Stream and extra headers

+ +
//read 12 bytes from Serial and send them as Content Type text/plain
+AsyncWebServerResponse *response = request->beginResponse(Serial, "text/plain", 12);
+response->addHeader("Server","ESP Async Web Server");
+request->send(response);
+ +

+Respond with content coming from a File

+ +
//Send index.htm with default content type
+request->send(SPIFFS, "/index.htm");
+
+//Send index.htm as text
+request->send(SPIFFS, "/index.htm", "text/plain");
+
+//Download index.htm
+request->send(SPIFFS, "/index.htm", String(), true);
+ +

+Respond with content coming from a File and extra headers

+ +
//Send index.htm with default content type
+AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/index.htm");
+
+//Send index.htm as text
+AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/index.htm", "text/plain");
+
+//Download index.htm
+AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/index.htm", String(), true);
+
+response->addHeader("Server","ESP Async Web Server");
+request->send(response);
+ +

+Respond with content using a callback

+ +
//send 128 bytes as plain text
+request->send("text/plain", 128, [](uint8_t *buffer, size_t maxLen, size_t index) -> size_t {
+  //Write up to "maxLen" bytes into "buffer" and return the amount written.
+  //index equals the amount of bytes that have been already sent
+  //You will not be asked for more bytes once the content length has been reached.
+  //Keep in mind that you can not delay or yield waiting for more data!
+  //Send what you currently have and you will be asked for more again
+  return mySource.read(buffer, maxLen);
+});
+ +

+Respond with content using a callback and extra headers

+ +
//send 128 bytes as plain text
+AsyncWebServerResponse *response = request->beginResponse("text/plain", 128, [](uint8_t *buffer, size_t maxLen, size_t index) -> size_t {
+  //Write up to "maxLen" bytes into "buffer" and return the amount written.
+  //index equals the amount of bytes that have been already sent
+  //You will not be asked for more bytes once the content length has been reached.
+  //Keep in mind that you can not delay or yield waiting for more data!
+  //Send what you currently have and you will be asked for more again
+  return mySource.read(buffer, maxLen);
+});
+response->addHeader("Server","ESP Async Web Server");
+request->send(response);
+ +

+Chunked Response

+ +

Used when content length is unknown. Works best if the client supports HTTP/1.1

+ +
AsyncWebServerResponse *response = request->beginChunkedResponse("text/plain", [](uint8_t *buffer, size_t maxLen, size_t index) -> size_t {
+  //Write up to "maxLen" bytes into "buffer" and return the amount written.
+  //index equals the amount of bytes that have been already sent
+  //You will be asked for more data until 0 is returned
+  //Keep in mind that you can not delay or yield waiting for more data!
+  return mySource.read(buffer, maxLen);
+});
+response->addHeader("Server","ESP Async Web Server");
+request->send(response);
+ +

+Print to response

+ +
AsyncResponseStream *response = request->beginResponseStream("text/html");
+response->addHeader("Server","ESP Async Web Server");
+response->printf("<!DOCTYPE html><html><head><title>Webpage at %s</title></head><body>", request->url().c_str());
+
+response->print("<h2>Hello ");
+response->print(request->client()->remoteIP());
+response->print("</h2>");
+
+response->print("<h3>General</h3>");
+response->print("<ul>");
+response->printf("<li>Version: HTTP/1.%u</li>", request->version());
+response->printf("<li>Method: %s</li>", request->methodToString());
+response->printf("<li>URL: %s</li>", request->url().c_str());
+response->printf("<li>Host: %s</li>", request->host().c_str());
+response->printf("<li>ContentType: %s</li>", request->contentType().c_str());
+response->printf("<li>ContentLength: %u</li>", request->contentLength());
+response->printf("<li>Multipart: %s</li>", request->multipart()?"true":"false");
+response->print("</ul>");
+
+response->print("<h3>Headers</h3>");
+response->print("<ul>");
+int headers = request->headers();
+for(int i=0;i<headers;i++){
+  AsyncWebHeader* h = request->getHeader(i);
+  response->printf("<li>%s: %s</li>", h->name().c_str(), h->value().c_str());
+}
+response->print("</ul>");
+
+response->print("<h3>Parameters</h3>");
+response->print("<ul>");
+int params = request->params();
+for(int i=0;i<params;i++){
+  AsyncWebParameter* p = request->getParam(i);
+  if(p->isFile()){
+    response->printf("<li>FILE[%s]: %s, size: %u</li>", p->name().c_str(), p->value().c_str(), p->size());
+  } else if(p->isPost()){
+    response->printf("<li>POST[%s]: %s</li>", p->name().c_str(), p->value().c_str());
+  } else {
+    response->printf("<li>GET[%s]: %s</li>", p->name().c_str(), p->value().c_str());
+  }
+}
+response->print("</ul>");
+
+response->print("</body></html>");
+//send the response last
+request->send(response);
+ +

+Send a large webpage from PROGMEM using callback response

+ +

Example provided by @nouser2013

+ +
const char indexhtml[] PROGMEM = "..."; // large char array, tested with 5k
+AsyncWebServerResponse *response = request->beginResponse(
+  String("text/html"),
+  strlen_P(indexhtml),
+  [](uint8_t *buffer, size_t maxLen, size_t alreadySent) -> size_t {
+    if (strlen_P(indexhtml+alreadySent)>maxLen) {
+      // We have more to read than fits in maxLen Buffer
+      memcpy_P((char*)buffer, indexhtml+alreadySent, maxLen);
+      return maxLen;
+    }
+    // Ok, last chunk
+    memcpy_P((char*)buffer, indexhtml+alreadySent, strlen_P(indexhtml+alreadySent));
+    return strlen_P(indexhtml+alreadySent); // Return from here to end of indexhtml
+  }
+);
+response->addHeader("Server", "MyServerString");
+request->send(response);  
+ +

+ArduinoJson Basic Response

+ +

This way of sending Json is great for when the result is below 4KB

+ +
#include "AsyncJson.h"
+#include "ArduinoJson.h"
+
+
+AsyncResponseStream *response = request->beginResponseStream("text/json");
+DynamicJsonBuffer jsonBuffer;
+JsonObject &root = jsonBuffer.createObject();
+root["heap"] = ESP.getFreeHeap();
+root["ssid"] = WiFi.SSID();
+root.printTo(*response);
+request->send(response);
+ +

+ArduinoJson Advanced Response

+ +

This response can handle really large Json objects (tested to 40KB) +There isn't any noticeable speed decrease for small results with the method above +Since ArduinoJson does not allow reading parts of the string, the whole Json has to +be passed every time a chunks needs to be sent, which shows speed decrease proportional +to the resulting json packets

+ +
#include "AsyncJson.h"
+#include "ArduinoJson.h"
+
+
+AsyncJsonResponse * response = new AsyncJsonResponse();
+response->addHeader("Server","ESP Async Web Server");
+JsonObject& root = response->getRoot();
+root["heap"] = ESP.getFreeHeap();
+root["ssid"] = WiFi.SSID();
+response->setLength();
+request->send(response);
+ +

+Bad Responses

+ +

Some responses are implemented, but you should not use them, because they do not conform to HTTP. +The following example will lead to unclean close of the connection and more time wasted +than providing the length of the content

+ +

+Respond with content using a callback without content length to HTTP/1.0 clients

+ +
//This is used as fallback for chunked responses to HTTP/1.0 Clients
+request->send("text/plain", 0, [](uint8_t *buffer, size_t maxLen, size_t index) -> size_t {
+  //Write up to "maxLen" bytes into "buffer" and return the amount written.
+  //You will be asked for more data until 0 is returned
+  //Keep in mind that you can not delay or yield waiting for more data!
+  return mySource.read(buffer, maxLen);
+});
+ +

+Async WebSocket Plugin

+ +

The server includes a web socket plugin which lets you define different WebSocket locations to connect to +without starting another listening service or using different port

+ +

+Async WebSocket Event

+ +
+void onEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){
+  if(type == WS_EVT_CONNECT){
+    //client connected
+    os_printf("ws[%s][%u] connect\n", server->url(), client->id());
+    client->printf("Hello Client %u :)", client->id());
+    client->ping();
+  } else if(type == WS_EVT_DISCONNECT){
+    //client disconnected
+    os_printf("ws[%s][%u] disconnect: %u\n", server->url(), client->id());
+  } else if(type == WS_EVT_ERROR){
+    //error was received from the other end
+    os_printf("ws[%s][%u] error(%u): %s\n", server->url(), client->id(), *((uint16_t*)arg), (char*)data);
+  } else if(type == WS_EVT_PONG){
+    //pong message was received (in response to a ping request maybe)
+    os_printf("ws[%s][%u] pong[%u]: %s\n", server->url(), client->id(), len, (len)?(char*)data:"");
+  } else if(type == WS_EVT_DATA){
+    //data packet
+    AwsFrameInfo * info = (AwsFrameInfo*)arg;
+    if(info->num == 0 && info->final && info->index == 0 && info->len == len){
+      //the whole message is in a single frame and we got all of it's data
+      os_printf("ws[%s][%u] %s-message[%llu]: ", server->url(), client->id(), (info->opcode == WS_TEXT)?"text":"binary", info->len);
+      if(info->opcode == WS_TEXT){
+        data[len] = 0;
+        os_printf("%s\n", (char*)data);
+      } else {
+        for(size_t i=0; i < info->len; i++){
+          os_printf("%02x ", data[i]);
+        }
+        os_printf("\n");
+      }
+      if(info->opcode == WS_TEXT)
+        client->text("I got your text message");
+      else
+        client->binary("I got your binary message");
+    } else {
+      //message is comprised of multiple frames or the frame is split into multiple packets
+      if(info->index == 0){
+        if(info->num == 0)
+          os_printf("ws[%s][%u] %s-message start\n", server->url(), client->id(), (info->message_opcode == WS_TEXT)?"text":"binary");
+        os_printf("ws[%s][%u] frame[%u] start[%llu]\n", server->url(), client->id(), info->num, info->len);
+      }
+
+      os_printf("ws[%s][%u] frame[%u] %s[%llu - %llu]: ", server->url(), client->id(), info->num, (info->message_opcode == WS_TEXT)?"text":"binary", info->index, info->index + len);
+      if(info->message_opcode == WS_TEXT){
+        data[len] = 0;
+        os_printf("%s\n", (char*)data);
+      } else {
+        for(size_t i=0; i < len; i++){
+          os_printf("%02x ", data[i]);
+        }
+        os_printf("\n");
+      }
+
+      if((info->index + len) == info->len){
+        os_printf("ws[%s][%u] frame[%u] end[%llu]\n", server->url(), client->id(), info->num, info->len);
+        if(info->final){
+          os_printf("ws[%s][%u] %s-message end\n", server->url(), client->id(), (info->message_opcode == WS_TEXT)?"text":"binary");
+          if(info->message_opcode == WS_TEXT)
+            client->text("I got your text message");
+          else
+            client->binary("I got your binary message");
+        }
+      }
+    }
+  }
+}
+ +

+Methods for sending data to a socket client

+ +
//Server methods
+AsyncWebSocket ws("/ws");
+//printf to a client
+ws.printf([client id], [arguments...])
+//printf to all clients
+ws.printfAll([arguments...])
+//send text to a client
+ws.text([client id], [(char*)text])
+ws.text([client id], [text], [len])
+//send text to all clients
+ws.textAll([(char*text])
+ws.textAll([text], [len])
+//send binary to a client
+ws.binary([client id], [(char*)binary])
+ws.binary([client id], [binary], [len])
+//send binary to all clients
+ws.binaryAll([(char*binary])
+ws.binaryAll([binary], [len])
+
+//client methods
+AsyncWebSocketClient * client;
+//printf to a client
+client->printf([arguments...])
+//send text to a client
+client->text([(char*)text])
+client->text([text], [len])
+//send binary to a client
+client->binary([(char*)binary])
+client->binary([binary], [len])
+
+ +

+Setting up the server

+ +
#include "ESPAsyncTCP.h"
+#include "ESPAsyncWebServer.h"
+
+AsyncWebServer server(80);
+AsyncWebSocket control_ws("/control"); // access at ws://[esp ip]/control
+AsyncWebSocket data_ws("/data"); // access at ws://[esp ip]/data
+
+const char* ssid = "your-ssid";
+const char* password = "your-pass";
+const char* http_username = "admin";
+const char* http_password = "admin";
+
+void onRequest(AsyncWebServerRequest *request){
+  //Handle Unknown Request
+  request->send(404);
+}
+
+void onBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total){
+  //Handle body
+}
+
+void onUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final){
+  //Handle upload
+}
+
+void onEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){
+  //Handle WebSocket event
+}
+
+void setup(){
+  Serial.begin(115200);
+  WiFi.mode(WIFI_STA);
+  WiFi.begin(ssid, password);
+  if (WiFi.waitForConnectResult() != WL_CONNECTED) {
+    Serial.printf("WiFi Failed!\n");
+    return;
+  }
+
+  // attach Async WebSockets
+  control_ws.onEvent(onEvent);
+  server.addHandler(&control_ws);
+  data_ws.onEvent(onEvent);
+  server.addHandler(&data_ws);
+
+  // respond to GET requests on URL /heap
+  server.on("/heap", HTTP_GET, [](AsyncWebServerRequest *request){
+    request->send(200, "text/plain", String(ESP.getFreeHeap()));
+  });
+
+  // upload a file to /upload
+  server.on("/upload", HTTP_POST, [](AsyncWebServerRequest *request){
+    request->send(200);
+  }, handleUpload);
+
+  // send a file when /index is requested
+  server.on("/index", HTTP_ANY, [](AsyncWebServerRequest *request){
+    request->send(SPIFFS, "/index.htm");
+  });
+
+  // HTTP basic authentication
+  server.on("/login", HTTP_GET, [](AsyncWebServerRequest *request){
+    if(!request->authenticate(http_username, http_password))
+        return request->requestAuthentication();
+    request->send(200, "text/plain", "Login Success!");
+  });
+
+  // attach filesystem root at URL /fs 
+  server.serveStatic("/fs", SPIFFS, "/");
+
+  // Catch-All Handlers
+  // Any request that can not find a Handler that canHandle it
+  // ends in the callbacks below.
+  server.onNotFound(onRequest);
+  server.onFileUpload(onUpload);
+  server.onRequestBody(onBody);
+
+  server.begin();
+}
+
+void loop(){}
+ + + +