Add WebServer, WebServerSecure, HTTPUpdateServer, HTTPUpdateServerSecure (#791)

* Add HTTP-parser lib to support ESP32 WebServer
* Add WebServer from ESP32.  Only supports HTTP
* Separate HTTP server from the network server
Instead of managing the WiFiServer/WiFiServerSecure in the same object
as the HTTP handling, split them into separate objects.  This lets
HTTP and HTTPS servers work without templates or duplicating code.
The HTTP block just gets a `WiFiClient*` and works with that to only
do HTTP processing, while the upper object handles the appropriate
server and client types.
* Add HTTPS server
* Clean up some THandlerFunction refs
* Refactor into a template-ized WebServer/WebServerSecure
* Add DNSServer examples which need WebServer
* Fix CoreMutex infinite recursion crash
Core could crash while Serial debugging was going on and prints were
happening from LWIP/IRQ land and the main app.
* Add HTTPUpdateServer(Secure)
* Add MIME include, optimize WebServer::send(size,len)
When send()ing a large buffer, the WebServer::send() call would
actually convert that buffer into a String (i.e. duplicate it, and
potential issues with embedded \0s in binary data).
Make a simple override to send(size, len) to allow writing from the
source buffer instead.
* Fix WiFiClient::send(Stream), add FSBrowser example
This commit was merged in pull request #791.
This commit is contained in:
Earle F. Philhower, III
2022-08-23 15:51:32 -07:00
committed by GitHub
parent c501306c4f
commit 0edba2ee2a
81 changed files with 6837 additions and 58 deletions
@@ -0,0 +1,161 @@
#pragma once
#include <Arduino.h>
#include <WiFiClient.h>
#include <WiFiServer.h>
#include <WebServer.h>
#include <WiFiUdp.h>
#include <LittleFS.h>
#include "StreamString.h"
#include "HTTPUpdateServer.h"
extern uint8_t _FS_start;
extern uint8_t _FS_end;
static const char serverIndex[] PROGMEM =
R"(<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width,initial-scale=1'/>
</head>
<body>
<form method='POST' action='' enctype='multipart/form-data'>
Firmware:<br>
<input type='file' accept='.bin,.bin.gz' name='firmware'>
<input type='submit' value='Update Firmware'>
</form>
<form method='POST' action='' enctype='multipart/form-data'>
FileSystem:<br>
<input type='file' accept='.bin,.bin.gz' name='filesystem'>
<input type='submit' value='Update FileSystem'>
</form>
</body>
</html>)";
static const char successResponse[] PROGMEM =
"<META http-equiv=\"refresh\" content=\"15;URL=/\">Update Success! Rebooting...";
template <typename ServerType, int ServerPort>
HTTPUpdateServerTemplate<ServerType, ServerPort>::HTTPUpdateServerTemplate(bool serial_debug) {
_serial_output = serial_debug;
_server = NULL;
_username = "";
_password = "";
_authenticated = false;
}
template <typename ServerType, int ServerPort>
void HTTPUpdateServerTemplate<ServerType, ServerPort>::setup(WebServerTemplate<ServerType, ServerPort> *server, const String& path, const String& username, const String& password) {
_server = server;
_username = username;
_password = password;
// handler for the /update form page
_server->on(path.c_str(), HTTP_GET, [&]() {
if (_username != "" && _password != "" && !_server->authenticate(_username.c_str(), _password.c_str())) {
return _server->requestAuthentication();
}
_server->send_P(200, PSTR("text/html"), serverIndex);
});
// handler for the /update form page - preflight options
_server->on(path.c_str(), HTTP_OPTIONS, [&]() {
_server->sendHeader("Access-Control-Allow-Headers", "*");
_server->sendHeader("Access-Control-Allow-Origin", "*");
_server->send(200, F("text/html"), String(F("y")));
}, [&]() {
_authenticated = (_username == "" || _password == "" || _server->authenticate(_username.c_str(), _password.c_str()));
if (!_authenticated) {
if (_serial_output) {
Serial.printf("Unauthenticated Update\n");
}
return;
}
});
// handler for the /update form POST (once file upload finishes)
_server->on(path.c_str(), HTTP_POST, [&]() {
_server->sendHeader("Access-Control-Allow-Headers", "*");
_server->sendHeader("Access-Control-Allow-Origin", "*");
if (!_authenticated) {
return _server->requestAuthentication();
}
if (Update.hasError()) {
_server->send(200, F("text/html"), String(F("Update error: ")) + _updaterError);
} else {
_server->client().setNoDelay(true);
_server->send_P(200, PSTR("text/html"), successResponse);
delay(100);
_server->client().stop();
rp2040.restart();
}
}, [&]() {
// handler for the file upload, gets the sketch bytes, and writes
// them through the Update object
HTTPUpload& upload = _server->upload();
if (upload.status == UPLOAD_FILE_START) {
_updaterError = "";
_authenticated = (_username == "" || _password == "" || _server->authenticate(_username.c_str(), _password.c_str()));
if (!_authenticated) {
if (_serial_output) {
Serial.printf("Unauthenticated Update\n");
}
return;
}
if (_serial_output) {
Serial.printf("Update: %s\n", upload.filename.c_str());
}
if (upload.name == "filesystem") {
size_t fsSize = ((size_t)&_FS_end - (size_t)&_FS_start);
LittleFS.end();
if (!Update.begin(fsSize, U_FS)) { //start with max available size
if (_serial_output) {
Update.printError(Serial);
}
}
} else {
FSInfo64 i;
LittleFS.begin();
LittleFS.info64(i);
uint32_t maxSketchSpace = i.totalBytes - i.usedBytes;
if (!Update.begin(maxSketchSpace, U_FLASH)) { //start with max available size
_setUpdaterError();
}
}
} else if (_authenticated && upload.status == UPLOAD_FILE_WRITE && !_updaterError.length()) {
if (_serial_output) {
Serial.printf(".");
}
if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
_setUpdaterError();
}
} else if (_authenticated && upload.status == UPLOAD_FILE_END && !_updaterError.length()) {
if (Update.end(true)) { //true to set the size to the current progress
if (_serial_output) {
Serial.printf("Update Success: %zu\nRebooting...\n", upload.totalSize);
}
} else {
_setUpdaterError();
}
} else if (_authenticated && upload.status == UPLOAD_FILE_ABORTED) {
Update.end();
if (_serial_output) {
Serial.println("Update was aborted");
}
}
});
}
template <typename ServerType, int ServerPort>
void HTTPUpdateServerTemplate<ServerType, ServerPort>::_setUpdaterError() {
if (_serial_output) {
Update.printError(Serial);
}
StreamString str;
Update.printError(str);
_updaterError = str.c_str();
}