Почему вы должны заботиться
- Использование асинхронного сети означает, что вы можете справиться с более чем одно соединение одновременно
- Вы призваны, как только запрос будет готов и проанализированы
- При отправке ответа, вы сразу же готовы обрабатывать другие соединения, пока сервер заботится об отправке ответа в фоновом режиме
- Скорость OMG
- Простой в использовании API, HTTP Basic и Digest MD5 аутентификации (по умолчанию), ChunkedResponse
- Легко расширяемый для обработки любого типа содержимого
- Поддержка Продолжить 100
- Асинхронный WebSocket плагин предлагает различные места без дополнительных серверов или портов
- Асинхронный EventSource (Server-Sent Events) плагин для отправки событий в браузере
- URL Rewrite модуль для условного и постоянного адреса переписывает
- ServeStatic плагин, который поддерживает кэш, Last-Modified, индекс по умолчанию и многого другого
Важные вещи, чтобы помнить
- Это полностью асинхронный сервер и как таковой не работает на нити петли.
- Вы не можете использовать выход или задержку или любую функцию, которая использует их внутри обратные вызовы
- Сервер достаточно умен, чтобы знать, когда, чтобы закрыть соединение и свободные ресурсы
- Вы не можете отправить более одного ответа на один запрос
Принципы работы
Веб-сервер Асинхронный
- Прослушивания для соединений
- Обертка новых клиентов в Request
- Отслеживание клиентов и очищает память
- Руководит Rewritesи применять их на URL запроса
- Руководит Handlersи прикрепляет их к запросам
Запрос жизненного цикла
- соединение TCP принимается сервером
- Соединение обернуто внутри Request объекта
- Когда головка запрос получен (тип, URL, получить Params, версия HTTP и хост), сервер проходит через все Rewrites(в порядке их добавления) , чтобы переписать URL и инъекционные параметры запроса, затем, она проходит через все подключенные к нему Handlers ( в том порядке , они были добавлены) , пытаясь найти то , что canHandleданный запрос. Если ничего не найдено, по умолчанию (догони-всех) обработчик прилагается.
- Остальная часть запроса принимается, назвав handleUpload или handleBody методы из , Handler если они необходимы (POST + File / Body)
- Когда весь запрос обрабатывается, результат отдается handleRequest методу из Handlerи готов быть дан ответ
- В handleRequest способе, к Request прикреплен Response объект (смотри ниже) , который будет служить данные ответа обратно клиенту
- Когда Response посылается, клиент закрывается и освобождается из памяти
Перезапись и как они работают
- Rewrites Используются для перезаписи запроса URL и / или вводить получить параметры для конкретного пути URL запроса.
- Все Rewrites оценивается по запросу в порядке , они были добавлены к серверу.
- RewriteИзменит URL запроса , только если URL запроса ( за исключением параметров GET) полностью соответствует переписывания URL, и когда дополнительный Filterобратный вызов возвращает истину.
- Установка Filterк Rewriteпозволяет контролировать , когда применять рерайт, решение может быть основано на URL запроса, HTTP версию, запрос хост / порт / целевой хост, получить параметры или localIP по просьбе клиента или remoteIP.
- Предусмотрены два обратных вызовов фильтра: ON_AP_FILTERвыполнить перезапись , когда запрос к интерфейсу AP, ON_STA_FILTERчтобы выполнить перезапись , когда сделан запрос к интерфейсу STA.
- RewriteМожно указать целевой URL с дополнительными параметрами, например ПОЛУЧИТЬ/to-url?with=params
Обработчики и как они работают
- Handlers Используются для выполнения конкретных действий в конкретные запросы
- Один Handler экземпляр может быть присоединен к любой просьбе и живет вместе с сервером
- Установка Filterк Handler позволяет контролировать , когда применить обработчик, решение может быть основано на URL запроса, HTTP версию, запрос хост / порт / целевой хост, получить параметры или localIP по просьбе клиента или remoteIP.
- Предусмотрены два обратных вызовов фильтра: ON_AP_FILTERвыполнить перезапись , когда запрос к интерфейсу AP, ON_STA_FILTERчтобы выполнить перезапись , когда сделан запрос к интерфейсу STA.
- canHandle Метод используется для обработчика конкретного управления, могут ли быть обработаны запросы и объявить любые интересные заголовки о том , что Requestследует проанализировать. Решение может быть основано на методе запроса, запрос URL, HTTP версии, запрос хост / порт / целевой хост и получить параметры
- После Handler прилагается к данному Request( canHandleвозвращается истинный) , который Handler берет на себя , чтобы получить какой – либо файл / загрузки данных и прикрепить Response когда Request был полностью разобран
- Handlers оцениваются в порядке , они прикрепляются к серверу. canHandle Вызывается только если , Filter который был установлен на Handler возвращение истинного.
- Первый , Handler который может обработать запрос выбран, не далее Filterи canHandle называются.
Ответы и как они работают
- Эти Response объекты используются для передачи данных ответа обратно клиенту
- Response Объект живет с Requestи освобождается от конца или отключения
- Различные методы используются в зависимости от типа отклика для передачи данных в пакетах, возвращающихся обратно почти сразу и отправки следующего пакета, когда этот один будет получен. Каждый раз, когда между ними проводятся для запуска цикла пользователя и обрабатывать другие сетевые пакеты
- В ответ асинхронно, вероятно, самое трудное для большинства, чтобы понять,
- Существует множество различных вариантов для пользователя, чтобы отвечать на фоновую задачу
Библиотеки и проекты, которые используют AsyncWebServer
- WebSocketToSerial – Debug последовательные устройства через веб – браузер
- Sattrack – Дорожка МКС с ESP8266
- ESP Радио – Icecast радио на основе ESP8266 и VS1053
- VZero – беспроводной контроллер нулевой конфигурации для volkszaehler.org
- ESPurna – ESPurna ( «Искра» в каталонском) обычае C прошивка для основанных ESP8266 интеллектуальных коммутаторов. Изначально он был разработан с ITead Sonoff в виду.
- fauxmoESP – Belkin WEMO эмулятор библиотеки для ESP8266.
Запрос Переменные
Общие переменные
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()); } //List all collected headers (Compatibility) int headers = request->headers(); int i; for(i=0;i<headers;i++){ Serial.printf("HEADER[%s]: %s\n", request->headerName(i).c_str(), request->header(i).c_str()); } //get specific header by name (Compatibility) if(request->hasHeader("MyHeader")){ Serial.printf("MyHeader: %s\n", request->header("MyHeader").c_str()); }
GET, параметры POST и FILE
//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 if(request->hasParam("download")) AsyncWebParameter* p = request->getParam("download"); //Check if POST (but not File) parameter exists if(request->hasParam("download", true)) AsyncWebParameter* p = request->getParam("download", true); //Check if FILE was uploaded if(request->hasParam("download", true, true)) AsyncWebParameter* p = request->getParam("download", true, true); //List all parameters (Compatibility) int args = request->args(); for(int i=0;i<args;i++){ Serial.printf("ARG[%s]: %s\n", request->argName(i).c_str(), request->arg(i).c_str()); } //Check if parameter exists (Compatibility) if(request->hasArg("download")) String arg = request->arg("download");
Обработка файлов Загрузить
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); } }
Обработка данных для тела
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); } }
Ответы
Перенаправление на другой URL
//to local url request->redirect("/login"); //to external url request->redirect("http://esp8266.com");
Основная реакция с HTTP кодекса
request->send(404); //Sends 404 File Not Found
Основная реакция с HTTP кодексом и дополнительными заголовками
AsyncWebServerResponse *response = request->beginResponse(404); //Sends 404 File Not Found response->addHeader("Server","ESP Async Web Server"); request->send(response);
Основные характеристики с содержанием строки
request->send(200, "text/plain", "Hello World!");
Основные характеристики с содержанием строки и дополнительными заголовками
AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", "Hello World!"); response->addHeader("Server","ESP Async Web Server"); request->send(response);
Отправить большой сайт с PROGMEM
const char index_html[] PROGMEM = "..."; // large char array, tested with 14k request->send_P(200, "text/html", index_html);
Отправить большой сайт из PROGMEM и дополнительных заголовков
const char index_html[] PROGMEM = "..."; // large char array, tested with 14k AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", index_html); response->addHeader("Server","ESP Async Web Server"); request->send(response);
Отправить бинарное содержимое из PROGMEM
//File: favicon.ico.gz, Size: 726 #define favicon_ico_gz_len 726 const uint8_t favicon_ico_gz[] PROGMEM = { 0x1F, 0x8B, 0x08, 0x08, 0x0B, 0x87, 0x90, 0x57, 0x00, 0x03, 0x66, 0x61, 0x76, 0x69, 0x63, 0x6F, 0x6E, 0x2E, 0x69, 0x63, 0x6F, 0x00, 0xCD, 0x53, 0x5F, 0x48, 0x9A, 0x51, 0x14, 0xBF, 0x62, 0x6D, 0x86, 0x96, 0xA9, 0x64, 0xD3, 0xFE, 0xA8, 0x99, 0x65, 0x1A, 0xB4, 0x8A, 0xA8, 0x51, 0x54, 0x23, 0xA8, 0x11, 0x49, 0x51, 0x8A, 0x34, 0x62, 0x93, 0x85, 0x31, 0x58, 0x44, 0x12, 0x45, 0x2D, 0x58, 0xF5, 0x52, 0x41, 0x10, 0x23, 0x82, 0xA0, 0x20, 0x98, 0x2F, 0xC1, 0x26, 0xED, 0xA1, 0x20, 0x89, 0x04, 0xD7, 0x83, 0x58, 0x20, 0x28, 0x04, 0xAB, 0xD1, 0x9B, 0x8C, 0xE5, 0xC3, 0x60, 0x32, 0x64, 0x0E, 0x56, 0xBF, 0x9D, 0xEF, 0xF6, 0x30, 0x82, 0xED, 0xAD, 0x87, 0xDD, 0x8F, 0xF3, 0xDD, 0x8F, 0x73, 0xCF, 0xEF, 0x9C, 0xDF, 0x39, 0xBF, 0xFB, 0x31, 0x26, 0xA2, 0x27, 0x37, 0x97, 0xD1, 0x5B, 0xCF, 0x9E, 0x67, 0x30, 0xA6, 0x66, 0x8C, 0x99, 0xC9, 0xC8, 0x45, 0x9E, 0x6B, 0x3F, 0x5F, 0x74, 0xA6, 0x94, 0x5E, 0xDB, 0xFF, 0xB2, 0xE6, 0xE7, 0xE7, 0xF9, 0xDE, 0xD6, 0xD6, 0x96, 0xDB, 0xD8, 0xD8, 0x78, 0xBF, 0xA1, 0xA1, 0xC1, 0xDA, 0xDC, 0xDC, 0x2C, 0xEB, 0xED, 0xED, 0x15, 0x9B, 0xCD, 0xE6, 0x4A, 0x83, 0xC1, 0xE0, 0x2E, 0x29, 0x29, 0x99, 0xD6, 0x6A, 0xB5, 0x4F, 0x75, 0x3A, 0x9D, 0x61, 0x75, 0x75, 0x95, 0xB5, 0xB7, 0xB7, 0xDF, 0xC8, 0xD1, 0xD4, 0xD4, 0xF4, 0xB0, 0xBA, 0xBA, 0xFA, 0x83, 0xD5, 0x6A, 0xFD, 0x5A, 0x5E, 0x5E, 0x9E, 0x28, 0x2D, 0x2D, 0x0D, 0x10, 0xC6, 0x4B, 0x98, 0x78, 0x5E, 0x5E, 0xDE, 0x95, 0x42, 0xA1, 0x40, 0x4E, 0x4E, 0xCE, 0x65, 0x76, 0x76, 0xF6, 0x47, 0xB5, 0x5A, 0x6D, 0x4F, 0x26, 0x93, 0xA2, 0xD6, 0xD6, 0x56, 0x8E, 0x6D, 0x69, 0x69, 0xD1, 0x11, 0x36, 0x62, 0xB1, 0x58, 0x60, 0x32, 0x99, 0xA0, 0xD7, 0xEB, 0x51, 0x58, 0x58, 0x88, 0xFC, 0xFC, 0x7C, 0x10, 0x16, 0x02, 0x56, 0x2E, 0x97, 0x43, 0x2A, 0x95, 0x42, 0x2C, 0x16, 0x23, 0x33, 0x33, 0x33, 0xAE, 0x52, 0xA9, 0x1E, 0x64, 0x65, 0x65, 0x71, 0x7C, 0x7D, 0x7D, 0xBD, 0x93, 0xEA, 0xFE, 0x30, 0x1A, 0x8D, 0xE8, 0xEC, 0xEC, 0xC4, 0xE2, 0xE2, 0x22, 0x6A, 0x6A, 0x6A, 0x40, 0x39, 0x41, 0xB5, 0x38, 0x4E, 0xC8, 0x33, 0x3C, 0x3C, 0x0C, 0x87, 0xC3, 0xC1, 0x6B, 0x54, 0x54, 0x54, 0xBC, 0xE9, 0xEB, 0xEB, 0x93, 0x5F, 0x5C, 0x5C, 0x30, 0x8A, 0x9D, 0x2E, 0x2B, 0x2B, 0xBB, 0xA2, 0x3E, 0x41, 0xBD, 0x21, 0x1E, 0x8F, 0x63, 0x6A, 0x6A, 0x0A, 0x81, 0x40, 0x00, 0x94, 0x1B, 0x3D, 0x3D, 0x3D, 0x42, 0x3C, 0x96, 0x96, 0x96, 0x70, 0x7E, 0x7E, 0x8E, 0xE3, 0xE3, 0x63, 0xF8, 0xFD, 0xFE, 0xB4, 0xD7, 0xEB, 0xF5, 0x8F, 0x8F, 0x8F, 0x5B, 0x68, 0x5E, 0x6F, 0x05, 0xCE, 0xB4, 0xE3, 0xE8, 0xE8, 0x08, 0x27, 0x27, 0x27, 0xD8, 0xDF, 0xDF, 0xC7, 0xD9, 0xD9, 0x19, 0x6C, 0x36, 0x1B, 0x36, 0x36, 0x36, 0x38, 0x9F, 0x85, 0x85, 0x05, 0xAC, 0xAF, 0xAF, 0x23, 0x1A, 0x8D, 0x22, 0x91, 0x48, 0x20, 0x16, 0x8B, 0xFD, 0xDA, 0xDA, 0xDA, 0x7A, 0x41, 0x33, 0x7E, 0x57, 0x50, 0x50, 0x80, 0x89, 0x89, 0x09, 0x84, 0xC3, 0x61, 0x6C, 0x6F, 0x6F, 0x23, 0x12, 0x89, 0xE0, 0xE0, 0xE0, 0x00, 0x43, 0x43, 0x43, 0x58, 0x5E, 0x5E, 0xE6, 0x9C, 0x7D, 0x3E, 0x1F, 0x46, 0x47, 0x47, 0x79, 0xBE, 0xBD, 0xBD, 0x3D, 0xE1, 0x3C, 0x1D, 0x0C, 0x06, 0x9F, 0x10, 0xB7, 0xC7, 0x84, 0x4F, 0xF6, 0xF7, 0xF7, 0x63, 0x60, 0x60, 0x00, 0x83, 0x83, 0x83, 0x18, 0x19, 0x19, 0xC1, 0xDC, 0xDC, 0x1C, 0x8F, 0x17, 0x7C, 0xA4, 0x27, 0xE7, 0x34, 0x39, 0x39, 0x89, 0x9D, 0x9D, 0x1D, 0x6E, 0x54, 0xE3, 0x13, 0xE5, 0x34, 0x11, 0x37, 0x49, 0x51, 0x51, 0xD1, 0x4B, 0xA5, 0x52, 0xF9, 0x45, 0x26, 0x93, 0x5D, 0x0A, 0xF3, 0x92, 0x48, 0x24, 0xA0, 0x6F, 0x14, 0x17, 0x17, 0xA3, 0xB6, 0xB6, 0x16, 0x5D, 0x5D, 0x5D, 0x7C, 0x1E, 0xBB, 0xBB, 0xBB, 0x9C, 0xD7, 0xE1, 0xE1, 0x21, 0x42, 0xA1, 0xD0, 0x6B, 0xD2, 0x45, 0x4C, 0x33, 0x12, 0x34, 0xCC, 0xA0, 0x19, 0x54, 0x92, 0x56, 0x0E, 0xD2, 0xD9, 0x43, 0xF8, 0xCF, 0x82, 0x56, 0xC2, 0xDC, 0xEB, 0xEA, 0xEA, 0x38, 0x7E, 0x6C, 0x6C, 0x4C, 0xE0, 0xFE, 0x9D, 0xB8, 0xBF, 0xA7, 0xFA, 0xAF, 0x56, 0x56, 0x56, 0xEE, 0x6D, 0x6E, 0x6E, 0xDE, 0xB8, 0x47, 0x55, 0x55, 0x55, 0x6C, 0x66, 0x66, 0x46, 0x44, 0xDA, 0x3B, 0x34, 0x1A, 0x4D, 0x94, 0xB0, 0x3F, 0x09, 0x7B, 0x45, 0xBD, 0xA5, 0x5D, 0x2E, 0x57, 0x8C, 0x7A, 0x73, 0xD9, 0xED, 0xF6, 0x3B, 0x84, 0xFF, 0xE7, 0x7D, 0xA6, 0x3A, 0x2C, 0x95, 0x4A, 0xB1, 0x8E, 0x8E, 0x0E, 0x6D, 0x77, 0x77, 0xB7, 0xCD, 0xE9, 0x74, 0x3E, 0x73, 0xBB, 0xDD, 0x8F, 0x3C, 0x1E, 0x8F, 0xE6, 0xF4, 0xF4, 0x94, 0xAD, 0xAD, 0xAD, 0xDD, 0xDE, 0xCF, 0x73, 0x0B, 0x0B, 0xB8, 0xB6, 0xE0, 0x5D, 0xC6, 0x66, 0xC5, 0xE4, 0x10, 0x4C, 0xF4, 0xF7, 0xD8, 0x59, 0xF2, 0x7F, 0xA3, 0xB8, 0xB4, 0xFC, 0x0F, 0xEE, 0x37, 0x70, 0xEC, 0x16, 0x4A, 0x7E, 0x04, 0x00, 0x00 }; AsyncWebServerResponse *response = request->beginResponse_P(200, "image/x-icon", favicon_ico_gz, favicon_ico_gz_len); response->addHeader("Content-Encoding", "gzip"); request->send(response);
Ответить с содержанием исходит от потока
//read 12 bytes from Serial and send them as Content Type text/plain request->send(Serial, "text/plain", 12);
Ответить с содержанием исходит от потока и дополнительных заголовков
//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);
Ответить с содержанием приходит из файла
//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);
Ответить с содержанием приходит из файла и дополнительных заголовков
//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);
Ответить с содержанием, используя функцию обратного вызова
//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); });
Ответить содержимым с помощью обратного вызова и дополнительных заголовков
//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);
Блочный ответ
Используется, когда длина содержимого неизвестно. Лучше всего, если клиент поддерживает 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);
Печать в ответ
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);
ArduinoJson Basic Response
Этот способ отправки JSon велик для того, когда результат ниже 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
Этот ответ может обрабатывать очень большие объекты JSon (испытанные на 40kb) Существуют не заметное снижение скорости для небольших результатов с помощью метода выше Поскольку ArduinoJson не позволяет чтения части строки, весь Json должен быть принят каждый раз, когда глыбы должно быть отправлено, которое показывает уменьшение скорости, пропорциональное полученные пакеты JSon
#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);
Обслуживание статических файлов
В дополнении к действующим файлам из SPIFFS , как описано выше, сервер обеспечивает специальный обработчик , который оптимизация производительности выступающих файлы из SPIFFS – AsyncStaticWebHandler. Используйте server.serveStatic()функцию для инициализации и добавить новый экземпляр AsyncStaticWebHandlerк серверу. Обработчик не будет обрабатывать запрос , если файл не существует, например , сервер будет продолжать искать другой обработчик , который может обработать запрос. Обратите внимание на то, что вы можете цепи инкубационных функций для настройки обработчика, или держать указатель , чтобы изменить его на более позднее время.
Обслуживание конкретного файла по имени
// Serve the file "/www/page.htm" when request url is "/page.htm" server.serveStatic("/page.htm", SPIFFS, "/www/page.htm");
Обслуживание файлов в директории
Для того, чтобы обслуживать файлы в директории, путь к файлам должен указать директорию в SPIFFS и заканчивается «/».
// Serve files in directory "/www/" when request url starts with "/" // Request to the root or none existing files will try to server the defualt // file name "index.htm" if exists server.serveStatic("/", SPIFFS, "/www/"); // Server with different default file server.serveStatic("/", SPIFFS, "/www/").setDefaultFile("default.html");
Обслуживание статических файлов с проверкой подлинности
server .serveStatic("/", SPIFFS, "/www/") .setDefaultFile("default.html") .setAuthentication("user", "pass");
Указание заголовка Cache-Control
Можно указать значение заголовка Cache-Control , чтобы уменьшить количество вызовов на сервер после того , как клиент загрузил файлы. Для получения дополнительной информации о значениях Cache-Control см Cache-Control
// Cache responses for 10 minutes (600 seconds) server.serveStatic("/", SPIFFS, "/www/").setCacheControl("max-age:600"); //*** Change Cache-Control after server setup *** // During setup - keep a pointer to the handler AsyncStaticWebHandler* handler = &server.serveStatic("/", SPIFFS, "/www/").setCacheControl("max-age:600"); // At a later event - change Cache-Control handler->setCacheControl("max-age:30");
Указание заголовка Date-Modified
Можно указать заголовок Date-Modified для того, чтобы сервер вернуть Not-Modified (304) ответ для запросов с «If-Modified-Since» заголовка с тем же значением, вместо того, чтобы отвечать на запросы с реальным содержимым файла.
// Update the date modified string every time files are updated server.serveStatic("/", SPIFFS, "/www/").setLastModified("Mon, 20 Jun 2016 14:00:00 GMT"); //*** Chage last modified value at a later stage *** // During setup - read last modified value from config or EEPROM String date_modified = loadDateModified(); AsyncStaticWebHandler* handler = &server.serveStatic("/", SPIFFS, "/www/"); handler->setLastModified(date_modified); // At a later event when files are updated String date_modified = getNewDateModfied(); saveDateModified(date_modified); // Save for next reset handler->setLastModified(date_modified);
Использование фильтров
Фильтры могут быть установлены на Rewriteили Handlerдля того , чтобы контролировать , когда применить переписывают и рассмотрим обработчик. Фильтр является функцией обратного вызова , которая оценивает запрос и возвращает логическое значение , trueчтобы включить этот пункт или falseисключить его. Два фильтра обратного вызова предусмотрены убедить:
- ON_STA_FILTER – возвращает истину, когда запросы сделаны к интерфейсу STA (режим станции).
ON_AP_FILTER – возвращает истину, когда запросы сделаны к интерфейсу AP (точка доступа).
Подавать различные файлы сайта в режиме AP
server.serveStatic("/", SPIFFS, "/www/").setFilter(ON_STA_FILTER); server.serveStatic("/", SPIFFS, "/ap/").setFilter(ON_AP_FILTER);
Перепишите различный индекс на AP
// Serve the file "/www/index-ap.htm" in AP, and the file "/www/index.htm" on STA server.rewrite("/", "index.htm"); server.rewrite("/index.htm", "index-ap.htm").setFilter(ON_AP_FILTER); server.serveStatic("/", SPIFFS, "/www/");
Обслуживание различных хостов
// Filter callback using request host bool filterOnHost1(AsyncWebServerRequest *request) { return request->host() == "host1"; } // Server setup: server files in "/host1/" to requests for "host1", and files in "/www/" otherwise. server.serveStatic("/", SPIFFS, "/host1/").setFilter(filterOnHost1); server.serveStatic("/", SPIFFS, "/www/");
Плохие ответы
Некоторые ответы будут реализованы, но вы не должны использовать их, потому что они не соответствуют HTTP. Следующий пример приведет к нечистому закрытию соединения и больше времени впустую, чем обеспечение длиной содержания
Ответить с содержанием, используя функцию обратного вызова без длины контента для HTTP / 1.0 клиентов
//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); });
Асинхронный WebSocket Plugin
Сервер включает в себя веб-сокетов плагин, который позволяет определить различные места WebSocket для подключения без запуска другого сервиса прослушивания или использования другого порта
Асинхронный WebSocket события
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->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"); } } } } }
Методы отправки данных клиента сокета
//Server methods AsyncWebSocket ws("/ws"); //printf to a client ws.printf((uint32_t)client_id, arguments...); //printf to all clients ws.printfAll(arguments...); //printf_P to a client ws.printf_P((uint32_t)client_id, PSTR(format), arguments...); //printfAll_P to all clients ws.printf_P(PSTR(format), arguments...); //send text to a client ws.text((uint32_t)client_id, (char*)text); ws.text((uint32_t)client_id, (uint8_t*)text, (size_t)len); //send text from PROGMEM to a client ws.text((uint32_t)client_id, PSTR("text")); const char flash_text[] PROGMEM = "Text to send" ws.text((uint32_t)client_id, FPSTR(flash_text)); //send text to all clients ws.textAll((char*)text); ws.textAll((uint8_t*)text, (size_t)len); //send binary to a client ws.binary((uint32_t)client_id, (char*)binary); ws.binary((uint32_t)client_id, (uint8_t*)binary, (size_t)len); //send binary from PROGMEM to a client const uint8_t flash_binary[] PROGMEM = { 0x01, 0x02, 0x03, 0x04 }; ws.binary((uint32_t)client_id, flash_binary, 4); //send binary to all clients ws.binaryAll((char*)binary); ws.binaryAll((uint8_t*)binary, (size_t)len); //HTTP Authenticate before switch to Websocket protocol ws.setAuthentication("user", "pass"); //client methods AsyncWebSocketClient * client; //printf client->printf(arguments...); //printf_P client->printf_P(PSTR(format), arguments...); //send text client->text((char*)text); client->text((uint8_t*)text, (size_t)len); //send text from PROGMEM client->text(PSTR("text")); const char flash_text[] PROGMEM = "Text to send"; client->text(FPSTR(flash_text)); //send binary client->binary((char*)binary); client->binary((uint8_t*)binary, (size_t)len); //send binary from PROGMEM const uint8_t flash_binary[] PROGMEM = { 0x01, 0x02, 0x03, 0x04 }; client->binary(flash_binary, 4);
Прямой доступ к веб-буфера сокета сообщение
При отправке сообщения веб-сокетов с использованием вышеуказанных методов создается буфер. При определенных обстоятельствах вы можете манипулировать или заполнить этот буфер непосредственно из приложения, например, для предотвращения ненужного дублирования данных. Этот пример показывает, как создать буфер и печатать данные из объекта ArduinoJson затем отправить его.
void sendDataWs(AsyncWebSocketClient * client) { DynamicJsonBuffer jsonBuffer; JsonObject& root = jsonBuffer.createObject(); root["a"] = "abc"; root["b"] = "abcd"; root["c"] = "abcde"; root["d"] = "abcdef"; root["e"] = "abcdefg"; size_t len = root.measureLength(); AsyncWebSocketMessageBuffer * buffer = ws.makeBuffer(len); // creates a buffer (len + 1) for you. if (buffer) { root.printTo((char *)buffer->get(), len + 1); if (client) { client->text(buffer); } else { ws.textAll(buffer); } } }
Асинхронный Источник события Plugin
Сервер включает EventSource (Сервер-Sent Events) плагин, который может быть использован для отправки коротких текстовых событий в браузере. Разница между WebSockets и событий от является то, что это EventSource одностороннего действия, только текст протокола.
Настройка источника событий на сервере
syncWebServer server(80); AsyncEventSource events("/events"); void setup(){ // setup ...... events.onConnect([](AsyncEventSourceClient *client){ if(client->lastId()){ Serial.printf("Client reconnected! Last message ID that it gat is: %u\n", client->lastId()); } //send event with message "hello!", id current millis // and set reconnect delay to 1 second client->send("hello!",NULL,millis(),1000); }); //HTTP Basic authentication events.setAuthentication("user", "pass"); server.addHandler(&events); // setup ...... } void loop(){ if(eventTriggered){ // your logic here //send event "myevent" events.send("my event content","myevent",millis()); } }
Настройка Источник события в браузере
if (!!window.EventSource) { var source = new EventSource('/events'); source.addEventListener('open', function(e) { console.log("Events Connected"); }, false); source.addEventListener('error', function(e) { if (e.target.readyState != EventSource.OPEN) { console.log("Events Disconnected"); } }, false); source.addEventListener('message', function(e) { console.log("message", e.data); }, false); source.addEventListener('myevent', function(e) { console.log("myevent", e.data); }, false); }
Сканирование доступных Wi-Fi сетей
//First request will return 0 results unless you start scan from somewhere else (loop/setup) //Do not request more often than 3-5 seconds server.on("/scan", HTTP_GET, [](AsyncWebServerRequest *request){ String json = "["; int n = WiFi.scanComplete(); if(n == -2){ WiFi.scanNetworks(true); } else if(n){ for (int i = 0; i < n; ++i){ if(i) json += ","; json += "{"; json += "\"rssi\":"+String(WiFi.RSSI(i)); json += ",\"ssid\":\""+WiFi.SSID(i)+"\""; json += ",\"bssid\":\""+WiFi.BSSIDstr(i)+"\""; json += ",\"channel\":"+String(WiFi.channel(i)); json += ",\"secure\":"+String(WiFi.encryptionType(i)); json += ",\"hidden\":"+String(WiFi.isHidden(i)?"true":"false"); json += "}"; } WiFi.scanDelete(); if(WiFi.scanComplete() == -2){ WiFi.scanNetworks(true); } } json += "]"; request->send(200, "text/json", json); json = String(); });
Удалить обработчик и переписывает
Сервер проходит через обработчик в том порядке, в котором они были добавлены. Вы не можете просто добавить обработчик с таким же путем, чтобы переопределить их. Для удаления обработчика:
// save callback for particular URL path auto handler = server.on("/some/path", [](AsyncWebServerRequest *request){ //do something useful }); // when you don't need handler anymore remove it server.removeHandler(&handler); // same with rewrites server.removeRewrite(&someRewrite); server.onNotFound([](AsyncWebServerRequest *request){ request->send(404); }); // remove server.onNotFound handler server.onNotFound(NULL); // remove all rewrites, handlers and onNotFound/onFileUpload/onRequestBody callbacks server.reset();
Настройка сервера
#include "ESPAsyncTCP.h" #include "ESPAsyncWebServer.h" AsyncWebServer server(80); AsyncWebSocket ws("/ws"); // access at ws://[esp ip]/ws AsyncEventSource events("/events"); // event source (Server-Sent events) const char* ssid = "your-ssid"; const char* password = "your-pass"; const char* http_username = "admin"; const char* http_password = "admin"; //flag to use from web update to reboot the ESP bool shouldReboot = false; 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 AsyncWebSocket ws.onEvent(onEvent); server.addHandler(&ws); // attach AsyncEventSource server.addHandler(&events); // 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); }, onUpload); // 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!"); }); // Simple Firmware Update Form server.on("/update", HTTP_GET, [](AsyncWebServerRequest *request){ request->send(200, "text/html", "<form method='POST' action='/update' enctype='multipart/form-data'><input type='file' name='update'><input type='submit' value='Update'></form>"); }); server.on("/update", HTTP_POST, [](AsyncWebServerRequest *request){ shouldReboot = !Update.hasError(); AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", shouldReboot?"OK":"FAIL"); response->addHeader("Connection", "close"); request->send(response); },[](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final){ if(!index){ Serial.printf("Update Start: %s\n", filename.c_str()); Update.runAsync(true); if(!Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000)){ Update.printError(Serial); } } if(!Update.hasError()){ if(Update.write(data, len) != len){ Update.printError(Serial); } } if(final){ if(Update.end(true)){ Serial.printf("Update Success: %uB\n", index+len); } else { Update.printError(Serial); } } }); // 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(){ if(shouldReboot){ Serial.println("Rebooting..."); delay(100); ESP.restart(); } static char temp[128]; sprintf(temp, "Seconds since boot: %u", millis()/1000); events.send(temp, "time"); //send event "time" }
Настройка глобальных и класса функции как обработчики запросов
#include <Arduino.h> #include <ESPAsyncWebserver.h> #include <Hash.h> #include <functional> void handleRequest(AsyncWebServerRequest *request) { } class WebClass { public : WebClass(){ }; AsyncWebServer classWebServer = AsyncWebServer(80); void classRequest (AsyncWebServerRequest *request) { } void begin(){ // attach global request handler classWebServer.on("/example", HTTP_ANY, handleRequest); // attach class request handler classWebServer.on("/example", HTTP_ANY, std::bind(&WebClass::classRequest, this, std::placeholders::_1)); } }; AsyncWebServer globalWebServer(80); WebClass webClassInstance; void setup() { // attach global request handler globalWebServer.on("/example", HTTP_ANY, handleRequest); // attach class request handler globalWebServer.on("/example", HTTP_ANY, std::bind(&WebClass::classRequest, webClassInstance, std::placeholders::_1)); } void loop() { }
Методы контроля соединения WebSocket
// Disable client connections if it was activated if ( ws.enabled() ) ws.enable(false); // enable client connections if it was disabled if ( !ws.enabled() ) ws.enable(true);
Пример кода OTA
// OTA callbacks ArduinoOTA.onStart([]() { // Clean SPIFFS SPIFFS.end(); // Disable client connections ws.enable(false); // Advertise connected clients what's going on ws.textAll("OTA Update Started"); // Close them ws.closeAll(); });