3.3.7
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
Copyright (c) 2015, Majenko Technologies
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* * Redistributions in binary form must reproduce the above copyright notice, this
|
||||
list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
* * Neither the name of Majenko Technologies nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <WiFi.h>
|
||||
#include <NetworkClient.h>
|
||||
#include <WebServer.h>
|
||||
#include <ESPmDNS.h>
|
||||
|
||||
const char *ssid = "YourSSIDHere";
|
||||
const char *password = "YourPSKHere";
|
||||
|
||||
WebServer server(80);
|
||||
|
||||
const int led = 13;
|
||||
|
||||
void handleRoot() {
|
||||
digitalWrite(led, 1);
|
||||
char temp[400];
|
||||
int sec = millis() / 1000;
|
||||
int hr = sec / 3600;
|
||||
int min = (sec / 60) % 60;
|
||||
sec = sec % 60;
|
||||
|
||||
snprintf(
|
||||
temp, 400,
|
||||
|
||||
"<html>\
|
||||
<head>\
|
||||
<meta http-equiv='refresh' content='5'/>\
|
||||
<title>ESP32 Demo</title>\
|
||||
<style>\
|
||||
body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; Color: #000088; }\
|
||||
</style>\
|
||||
</head>\
|
||||
<body>\
|
||||
<h1>Hello from ESP32!</h1>\
|
||||
<p>Uptime: %02d:%02d:%02d</p>\
|
||||
<img src=\"/test.svg\" />\
|
||||
</body>\
|
||||
</html>",
|
||||
|
||||
hr, min, sec
|
||||
);
|
||||
server.send(200, "text/html", temp);
|
||||
digitalWrite(led, 0);
|
||||
}
|
||||
|
||||
void handleNotFound() {
|
||||
digitalWrite(led, 1);
|
||||
String message = "File Not Found\n\n";
|
||||
message += "URI: ";
|
||||
message += server.uri();
|
||||
message += "\nMethod: ";
|
||||
message += (server.method() == HTTP_GET) ? "GET" : "POST";
|
||||
message += "\nArguments: ";
|
||||
message += server.args();
|
||||
message += "\n";
|
||||
|
||||
for (uint8_t i = 0; i < server.args(); i++) {
|
||||
message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
|
||||
}
|
||||
|
||||
server.send(404, "text/plain", message);
|
||||
digitalWrite(led, 0);
|
||||
}
|
||||
|
||||
void setup(void) {
|
||||
pinMode(led, OUTPUT);
|
||||
digitalWrite(led, 0);
|
||||
Serial.begin(115200);
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.begin(ssid, password);
|
||||
Serial.println("");
|
||||
|
||||
// Wait for connection
|
||||
while (WiFi.status() != WL_CONNECTED) {
|
||||
delay(500);
|
||||
Serial.print(".");
|
||||
}
|
||||
|
||||
Serial.println("");
|
||||
Serial.print("Connected to ");
|
||||
Serial.println(ssid);
|
||||
Serial.print("IP address: ");
|
||||
Serial.println(WiFi.localIP());
|
||||
|
||||
if (MDNS.begin("esp32")) {
|
||||
Serial.println("MDNS responder started");
|
||||
}
|
||||
|
||||
server.on("/", handleRoot);
|
||||
server.on("/test.svg", drawGraph);
|
||||
server.on("/inline", []() {
|
||||
server.send(200, "text/plain", "this works as well");
|
||||
});
|
||||
server.onNotFound(handleNotFound);
|
||||
server.begin();
|
||||
Serial.println("HTTP server started");
|
||||
}
|
||||
|
||||
void loop(void) {
|
||||
server.handleClient();
|
||||
delay(2); //allow the cpu to switch to other tasks
|
||||
}
|
||||
|
||||
void drawGraph() {
|
||||
String out = "";
|
||||
char temp[100];
|
||||
out += "<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" width=\"400\" height=\"150\">\n";
|
||||
out += "<rect width=\"400\" height=\"150\" fill=\"rgb(250, 230, 210)\" stroke-width=\"1\" stroke=\"rgb(0, 0, 0)\" />\n";
|
||||
out += "<g stroke=\"black\">\n";
|
||||
int y = rand() % 130;
|
||||
for (int x = 10; x < 390; x += 10) {
|
||||
int y2 = rand() % 130;
|
||||
sprintf(temp, "<line x1=\"%d\" y1=\"%d\" x2=\"%d\" y2=\"%d\" stroke-width=\"1\" />\n", x, 140 - y, x + 10, 140 - y2);
|
||||
out += temp;
|
||||
y = y2;
|
||||
}
|
||||
out += "</g>\n</svg>\n";
|
||||
|
||||
server.send(200, "image/svg+xml", out);
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
requires_any:
|
||||
- CONFIG_SOC_WIFI_SUPPORTED=y
|
||||
- CONFIG_ESP_WIFI_REMOTE_ENABLED=y
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* This example demonstrates how to send an HTTP response using chunks
|
||||
* It will create an HTTP Server (port 80) associated with an a MDNS service
|
||||
* Access the HTTP server using a Web Browser:
|
||||
* URL can be composed using the MDNS name "esp32_chunk_resp.local"
|
||||
* http://esp32_chunk_resp.local/
|
||||
* or the IP Address that will be printed out, such as for instance 192.168.1.10
|
||||
* http://192.168.1.10/
|
||||
*
|
||||
* ESP32 Server response can also be viewed using the curl command:
|
||||
* curl -i esp32_chunk_resp.local:80
|
||||
* curl -i --raw esp32_chunk_resp.local:80
|
||||
*/
|
||||
|
||||
#include <WiFi.h>
|
||||
#include <NetworkClient.h>
|
||||
#include <WebServer.h>
|
||||
#include <ESPmDNS.h>
|
||||
|
||||
const char *ssid = "........";
|
||||
const char *password = "........";
|
||||
|
||||
WebServer server(80);
|
||||
|
||||
void handleChunks() {
|
||||
uint8_t countDown = 10;
|
||||
server.chunkResponseBegin();
|
||||
char countContent[8];
|
||||
while (countDown) {
|
||||
sprintf(countContent, "%d...\r\n", countDown--);
|
||||
server.chunkWrite(countContent, strlen(countContent));
|
||||
// count down shall show up in the browser only after about 5 seconds when finishing the whole transmission
|
||||
// using "curl -i esp32_chunk_resp.local:80", it will show the count down as it sends each chunk
|
||||
delay(500);
|
||||
}
|
||||
server.chunkWrite("DONE!", 5);
|
||||
server.chunkResponseEnd();
|
||||
}
|
||||
|
||||
void setup(void) {
|
||||
Serial.begin(115200);
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.begin(ssid, password);
|
||||
Serial.println("");
|
||||
|
||||
// Wait for connection
|
||||
while (WiFi.status() != WL_CONNECTED) {
|
||||
delay(500);
|
||||
Serial.print(".");
|
||||
}
|
||||
Serial.println("");
|
||||
Serial.print("Connected to ");
|
||||
Serial.println(ssid);
|
||||
Serial.print("IP address: ");
|
||||
Serial.println(WiFi.localIP());
|
||||
|
||||
// Use the URL: http://esp32_chunk_resp.local/
|
||||
if (MDNS.begin("esp32_chunk_resp")) {
|
||||
Serial.println("MDNS responder started");
|
||||
}
|
||||
|
||||
server.on("/", handleChunks);
|
||||
|
||||
server.onNotFound([]() {
|
||||
server.send(404, "text/plain", "Page not found");
|
||||
});
|
||||
|
||||
server.begin();
|
||||
Serial.println("HTTP server started");
|
||||
}
|
||||
|
||||
void loop(void) {
|
||||
server.handleClient();
|
||||
delay(2); //allow the cpu to switch to other tasks
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
requires_any:
|
||||
- CONFIG_SOC_WIFI_SUPPORTED=y
|
||||
- CONFIG_ESP_WIFI_REMOTE_ENABLED=y
|
||||
@@ -0,0 +1,308 @@
|
||||
/*
|
||||
FSWebServer - Example WebServer with FS backend for esp8266/esp32
|
||||
Copyright (c) 2015 Hristo Gochkov. All rights reserved.
|
||||
This file is part of the WebServer library for Arduino environment.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
upload the contents of the data folder with MkSPIFFS Tool ("ESP32 Sketch Data Upload" in Tools menu in Arduino IDE)
|
||||
or you can upload the contents of a folder if you CD in that folder and run the following command:
|
||||
for file in `ls -A1`; do curl -F "file=@$PWD/$file" esp32fs.local/edit; done
|
||||
|
||||
access the sample web page at http://esp32fs.local
|
||||
edit the page by going to http://esp32fs.local/edit
|
||||
*/
|
||||
#include <WiFi.h>
|
||||
#include <NetworkClient.h>
|
||||
#include <WebServer.h>
|
||||
#include <ESPmDNS.h>
|
||||
|
||||
#define FILESYSTEM SPIFFS
|
||||
// You only need to format the filesystem once
|
||||
#define FORMAT_FILESYSTEM false
|
||||
#define DBG_OUTPUT_PORT Serial
|
||||
|
||||
#if FILESYSTEM == FFat
|
||||
#include <FFat.h>
|
||||
#endif
|
||||
#if FILESYSTEM == SPIFFS
|
||||
#include <SPIFFS.h>
|
||||
#endif
|
||||
|
||||
const char *ssid = "wifi-ssid";
|
||||
const char *password = "wifi-password";
|
||||
const char *host = "esp32fs";
|
||||
WebServer server(80);
|
||||
//holds the current upload
|
||||
File fsUploadFile;
|
||||
|
||||
//format bytes
|
||||
String formatBytes(size_t bytes) {
|
||||
if (bytes < 1024) {
|
||||
return String(bytes) + "B";
|
||||
} else if (bytes < (1024 * 1024)) {
|
||||
return String(bytes / 1024.0) + "KB";
|
||||
} else if (bytes < (1024 * 1024 * 1024)) {
|
||||
return String(bytes / 1024.0 / 1024.0) + "MB";
|
||||
} else {
|
||||
return String(bytes / 1024.0 / 1024.0 / 1024.0) + "GB";
|
||||
}
|
||||
}
|
||||
|
||||
String getContentType(String filename) {
|
||||
if (server.hasArg("download")) {
|
||||
return "application/octet-stream";
|
||||
} else if (filename.endsWith(".htm")) {
|
||||
return "text/html";
|
||||
} else if (filename.endsWith(".html")) {
|
||||
return "text/html";
|
||||
} else if (filename.endsWith(".css")) {
|
||||
return "text/css";
|
||||
} else if (filename.endsWith(".js")) {
|
||||
return "application/javascript";
|
||||
} else if (filename.endsWith(".png")) {
|
||||
return "image/png";
|
||||
} else if (filename.endsWith(".gif")) {
|
||||
return "image/gif";
|
||||
} else if (filename.endsWith(".jpg")) {
|
||||
return "image/jpeg";
|
||||
} else if (filename.endsWith(".ico")) {
|
||||
return "image/x-icon";
|
||||
} else if (filename.endsWith(".xml")) {
|
||||
return "text/xml";
|
||||
} else if (filename.endsWith(".pdf")) {
|
||||
return "application/x-pdf";
|
||||
} else if (filename.endsWith(".zip")) {
|
||||
return "application/x-zip";
|
||||
} else if (filename.endsWith(".gz")) {
|
||||
return "application/x-gzip";
|
||||
}
|
||||
return "text/plain";
|
||||
}
|
||||
|
||||
bool exists(String path) {
|
||||
bool yes = false;
|
||||
File file = FILESYSTEM.open(path, "r");
|
||||
if (!file.isDirectory()) {
|
||||
yes = true;
|
||||
}
|
||||
file.close();
|
||||
return yes;
|
||||
}
|
||||
|
||||
bool handleFileRead(String path) {
|
||||
DBG_OUTPUT_PORT.println("handleFileRead: " + path);
|
||||
if (path.endsWith("/")) {
|
||||
path += "index.htm";
|
||||
}
|
||||
String contentType = getContentType(path);
|
||||
String pathWithGz = path + ".gz";
|
||||
if (exists(pathWithGz) || exists(path)) {
|
||||
if (exists(pathWithGz)) {
|
||||
path += ".gz";
|
||||
}
|
||||
File file = FILESYSTEM.open(path, "r");
|
||||
server.streamFile(file, contentType);
|
||||
file.close();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void handleFileUpload() {
|
||||
if (server.uri() != "/edit") {
|
||||
return;
|
||||
}
|
||||
HTTPUpload &upload = server.upload();
|
||||
if (upload.status == UPLOAD_FILE_START) {
|
||||
String filename = upload.filename;
|
||||
if (!filename.startsWith("/")) {
|
||||
filename = "/" + filename;
|
||||
}
|
||||
DBG_OUTPUT_PORT.print("handleFileUpload Name: ");
|
||||
DBG_OUTPUT_PORT.println(filename);
|
||||
fsUploadFile = FILESYSTEM.open(filename, "w");
|
||||
filename = String();
|
||||
} else if (upload.status == UPLOAD_FILE_WRITE) {
|
||||
//DBG_OUTPUT_PORT.print("handleFileUpload Data: "); DBG_OUTPUT_PORT.println(upload.currentSize);
|
||||
if (fsUploadFile) {
|
||||
fsUploadFile.write(upload.buf, upload.currentSize);
|
||||
}
|
||||
} else if (upload.status == UPLOAD_FILE_END) {
|
||||
if (fsUploadFile) {
|
||||
fsUploadFile.close();
|
||||
}
|
||||
DBG_OUTPUT_PORT.print("handleFileUpload Size: ");
|
||||
DBG_OUTPUT_PORT.println(upload.totalSize);
|
||||
}
|
||||
}
|
||||
|
||||
void handleFileDelete() {
|
||||
if (server.args() == 0) {
|
||||
return server.send(500, "text/plain", "BAD ARGS");
|
||||
}
|
||||
String path = server.arg(0);
|
||||
DBG_OUTPUT_PORT.println("handleFileDelete: " + path);
|
||||
if (path == "/") {
|
||||
return server.send(500, "text/plain", "BAD PATH");
|
||||
}
|
||||
if (!exists(path)) {
|
||||
return server.send(404, "text/plain", "FileNotFound");
|
||||
}
|
||||
FILESYSTEM.remove(path);
|
||||
server.send(200, "text/plain", "");
|
||||
path = String();
|
||||
}
|
||||
|
||||
void handleFileCreate() {
|
||||
if (server.args() == 0) {
|
||||
return server.send(500, "text/plain", "BAD ARGS");
|
||||
}
|
||||
String path = server.arg(0);
|
||||
DBG_OUTPUT_PORT.println("handleFileCreate: " + path);
|
||||
if (path == "/") {
|
||||
return server.send(500, "text/plain", "BAD PATH");
|
||||
}
|
||||
if (exists(path)) {
|
||||
return server.send(500, "text/plain", "FILE EXISTS");
|
||||
}
|
||||
File file = FILESYSTEM.open(path, "w");
|
||||
if (file) {
|
||||
file.close();
|
||||
} else {
|
||||
return server.send(500, "text/plain", "CREATE FAILED");
|
||||
}
|
||||
server.send(200, "text/plain", "");
|
||||
path = String();
|
||||
}
|
||||
|
||||
void handleFileList() {
|
||||
if (!server.hasArg("dir")) {
|
||||
server.send(500, "text/plain", "BAD ARGS");
|
||||
return;
|
||||
}
|
||||
|
||||
String path = server.arg("dir");
|
||||
DBG_OUTPUT_PORT.println("handleFileList: " + path);
|
||||
|
||||
File root = FILESYSTEM.open(path);
|
||||
path = String();
|
||||
|
||||
String output = "[";
|
||||
if (root.isDirectory()) {
|
||||
File file = root.openNextFile();
|
||||
while (file) {
|
||||
if (output != "[") {
|
||||
output += ',';
|
||||
}
|
||||
output += "{\"type\":\"";
|
||||
output += (file.isDirectory()) ? "dir" : "file";
|
||||
output += "\",\"name\":\"";
|
||||
output += String(file.path()).substring(1);
|
||||
output += "\"}";
|
||||
file = root.openNextFile();
|
||||
}
|
||||
}
|
||||
output += "]";
|
||||
server.send(200, "text/json", output);
|
||||
}
|
||||
|
||||
void setup(void) {
|
||||
DBG_OUTPUT_PORT.begin(115200);
|
||||
DBG_OUTPUT_PORT.print("\n");
|
||||
DBG_OUTPUT_PORT.setDebugOutput(true);
|
||||
if (FORMAT_FILESYSTEM) {
|
||||
FILESYSTEM.format();
|
||||
}
|
||||
FILESYSTEM.begin();
|
||||
{
|
||||
File root = FILESYSTEM.open("/");
|
||||
File file = root.openNextFile();
|
||||
while (file) {
|
||||
String fileName = file.name();
|
||||
size_t fileSize = file.size();
|
||||
DBG_OUTPUT_PORT.printf("FS File: %s, size: %s\n", fileName.c_str(), formatBytes(fileSize).c_str());
|
||||
file = root.openNextFile();
|
||||
}
|
||||
DBG_OUTPUT_PORT.printf("\n");
|
||||
}
|
||||
|
||||
//WIFI INIT
|
||||
DBG_OUTPUT_PORT.printf("Connecting to %s\n", ssid);
|
||||
if (String(WiFi.SSID()) != String(ssid)) {
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.begin(ssid, password);
|
||||
}
|
||||
|
||||
while (WiFi.status() != WL_CONNECTED) {
|
||||
delay(500);
|
||||
DBG_OUTPUT_PORT.print(".");
|
||||
}
|
||||
DBG_OUTPUT_PORT.println("");
|
||||
DBG_OUTPUT_PORT.print("Connected! IP address: ");
|
||||
DBG_OUTPUT_PORT.println(WiFi.localIP());
|
||||
|
||||
MDNS.begin(host);
|
||||
DBG_OUTPUT_PORT.print("Open http://");
|
||||
DBG_OUTPUT_PORT.print(host);
|
||||
DBG_OUTPUT_PORT.println(".local/edit to see the file browser");
|
||||
|
||||
//SERVER INIT
|
||||
//list directory
|
||||
server.on("/list", HTTP_GET, handleFileList);
|
||||
//load editor
|
||||
server.on("/edit", HTTP_GET, []() {
|
||||
if (!handleFileRead("/edit.htm")) {
|
||||
server.send(404, "text/plain", "FileNotFound");
|
||||
}
|
||||
});
|
||||
//create file
|
||||
server.on("/edit", HTTP_PUT, handleFileCreate);
|
||||
//delete file
|
||||
server.on("/edit", HTTP_DELETE, handleFileDelete);
|
||||
//first callback is called after the request has ended with all parsed arguments
|
||||
//second callback handles file uploads at that location
|
||||
server.on(
|
||||
"/edit", HTTP_POST,
|
||||
[]() {
|
||||
server.send(200, "text/plain", "");
|
||||
},
|
||||
handleFileUpload
|
||||
);
|
||||
|
||||
//called when the url is not defined here
|
||||
//use it to load content from FILESYSTEM
|
||||
server.onNotFound([]() {
|
||||
if (!handleFileRead(server.uri())) {
|
||||
server.send(404, "text/plain", "FileNotFound");
|
||||
}
|
||||
});
|
||||
|
||||
//get heap status, analog input value and all GPIO statuses in one json call
|
||||
server.on("/all", HTTP_GET, []() {
|
||||
String json = "{";
|
||||
json += "\"heap\":" + String(ESP.getFreeHeap());
|
||||
json += ", \"analog\":" + String(analogRead(A0));
|
||||
json += ", \"gpio\":" + String((uint32_t)(0));
|
||||
json += "}";
|
||||
server.send(200, "text/json", json);
|
||||
json = String();
|
||||
});
|
||||
server.begin();
|
||||
DBG_OUTPUT_PORT.println("HTTP server started");
|
||||
}
|
||||
|
||||
void loop(void) {
|
||||
server.handleClient();
|
||||
delay(2); //allow the cpu to switch to other tasks
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
requires_any:
|
||||
- CONFIG_SOC_WIFI_SUPPORTED=y
|
||||
- CONFIG_ESP_WIFI_REMOTE_ENABLED=y
|
||||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
@@ -0,0 +1,97 @@
|
||||
<!--
|
||||
FSWebServer - Example Index Page
|
||||
Copyright (c) 2015 Hristo Gochkov. All rights reserved.
|
||||
This file is part of the WebServer library for Arduino environment.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
-->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
|
||||
<title>ESP Monitor</title>
|
||||
<script type="text/javascript" src="graphs.js"></script>
|
||||
<script type="text/javascript">
|
||||
var heap,temp,digi;
|
||||
var reloadPeriod = 1000;
|
||||
var running = false;
|
||||
|
||||
function loadValues(){
|
||||
if(!running) return;
|
||||
var xh = new XMLHttpRequest();
|
||||
xh.onreadystatechange = function(){
|
||||
if (xh.readyState == 4){
|
||||
if(xh.status == 200) {
|
||||
var res = JSON.parse(xh.responseText);
|
||||
heap.add(res.heap);
|
||||
temp.add(res.analog);
|
||||
digi.add(res.gpio);
|
||||
if(running) setTimeout(loadValues, reloadPeriod);
|
||||
} else running = false;
|
||||
}
|
||||
};
|
||||
xh.open("GET", "/all", true);
|
||||
xh.send(null);
|
||||
};
|
||||
|
||||
function run(){
|
||||
if(!running){
|
||||
running = true;
|
||||
loadValues();
|
||||
}
|
||||
}
|
||||
|
||||
function onBodyLoad(){
|
||||
var refreshInput = document.getElementById("refresh-rate");
|
||||
refreshInput.value = reloadPeriod;
|
||||
refreshInput.onchange = function(e){
|
||||
var value = parseInt(e.target.value);
|
||||
reloadPeriod = (value > 0)?value:0;
|
||||
e.target.value = reloadPeriod;
|
||||
}
|
||||
var stopButton = document.getElementById("stop-button");
|
||||
stopButton.onclick = function(e){
|
||||
running = false;
|
||||
}
|
||||
var startButton = document.getElementById("start-button");
|
||||
startButton.onclick = function(e){
|
||||
run();
|
||||
}
|
||||
|
||||
// Example with 10K thermistor
|
||||
//function calcThermistor(v) {
|
||||
// var t = Math.log(((10230000 / v) - 10000));
|
||||
// t = (1/(0.001129148+(0.000234125*t)+(0.0000000876741*t*t*t)))-273.15;
|
||||
// return (t>120)?0:Math.round(t*10)/10;
|
||||
//}
|
||||
//temp = createGraph(document.getElementById("analog"), "Temperature", 100, 128, 10, 40, false, "cyan", calcThermistor);
|
||||
|
||||
temp = createGraph(document.getElementById("analog"), "Analog Input", 100, 128, 0, 1023, false, "cyan");
|
||||
heap = createGraph(document.getElementById("heap"), "Current Heap", 100, 125, 0, 30000, true, "orange");
|
||||
digi = createDigiGraph(document.getElementById("digital"), "GPIO", 100, 146, [0, 4, 5, 16], "gold");
|
||||
run();
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body id="index" style="margin:0; padding:0;" onload="onBodyLoad()">
|
||||
<div id="controls" style="display: block; border: 1px solid rgb(68, 68, 68); padding: 5px; margin: 5px; width: 362px; background-color: rgb(238, 238, 238);">
|
||||
<label>Period (ms):</label>
|
||||
<input type="number" id="refresh-rate"/>
|
||||
<input type="button" id="start-button" value="Start"/>
|
||||
<input type="button" id="stop-button" value="Stop"/>
|
||||
</div>
|
||||
<div id="heap"></div>
|
||||
<div id="analog"></div>
|
||||
<div id="digital"></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,110 @@
|
||||
#include <WiFi.h>
|
||||
#include <NetworkClient.h>
|
||||
#include <WebServer.h>
|
||||
#include <ESPmDNS.h>
|
||||
|
||||
// Your STA WiFi Credentials
|
||||
// ( This is the AP your ESP will connect to )
|
||||
const char *ssid = "........";
|
||||
const char *password = "........";
|
||||
|
||||
// Your AP WiFi Credentials
|
||||
// ( This is the AP your ESP will broadcast )
|
||||
const char *ap_ssid = "ESP32_Demo";
|
||||
const char *ap_password = "";
|
||||
|
||||
WebServer server(80);
|
||||
|
||||
const int led = 13;
|
||||
|
||||
// ON_STA_FILTER - Only accept requests coming from STA interface
|
||||
bool ON_STA_FILTER(WebServer &server) {
|
||||
return WiFi.STA.hasIP() && WiFi.STA.localIP() == server.client().localIP();
|
||||
}
|
||||
|
||||
// ON_AP_FILTER - Only accept requests coming from AP interface
|
||||
bool ON_AP_FILTER(WebServer &server) {
|
||||
return WiFi.AP.hasIP() && WiFi.AP.localIP() == server.client().localIP();
|
||||
}
|
||||
|
||||
void handleNotFound() {
|
||||
digitalWrite(led, 1);
|
||||
String message = "File Not Found\n\n";
|
||||
message += "URI: ";
|
||||
message += server.uri();
|
||||
message += "\nMethod: ";
|
||||
message += (server.method() == HTTP_GET) ? "GET" : "POST";
|
||||
message += "\nArguments: ";
|
||||
message += server.args();
|
||||
message += "\n";
|
||||
for (uint8_t i = 0; i < server.args(); i++) {
|
||||
message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
|
||||
}
|
||||
server.send(404, "text/plain", message);
|
||||
digitalWrite(led, 0);
|
||||
}
|
||||
|
||||
void setup(void) {
|
||||
pinMode(led, OUTPUT);
|
||||
digitalWrite(led, 0);
|
||||
Serial.begin(115200);
|
||||
WiFi.mode(WIFI_AP_STA);
|
||||
// Connect to STA
|
||||
WiFi.begin(ssid, password);
|
||||
// Start AP
|
||||
WiFi.softAP(ap_ssid, ap_password);
|
||||
Serial.println("");
|
||||
|
||||
// Wait for connection
|
||||
while (WiFi.status() != WL_CONNECTED) {
|
||||
delay(500);
|
||||
Serial.print(".");
|
||||
}
|
||||
Serial.println("");
|
||||
Serial.print("Connected to ");
|
||||
Serial.println(ssid);
|
||||
Serial.print("IP address: ");
|
||||
Serial.println(WiFi.localIP());
|
||||
|
||||
if (MDNS.begin("esp32")) {
|
||||
Serial.println("MDNS responder started");
|
||||
}
|
||||
|
||||
// This route will be accessible by STA clients only
|
||||
server
|
||||
.on(
|
||||
"/",
|
||||
[&]() {
|
||||
digitalWrite(led, 1);
|
||||
server.send(200, "text/plain", "Hi!, This route is accessible for STA clients only");
|
||||
digitalWrite(led, 0);
|
||||
}
|
||||
)
|
||||
.setFilter(ON_STA_FILTER);
|
||||
|
||||
// This route will be accessible by AP clients only
|
||||
server
|
||||
.on(
|
||||
"/",
|
||||
[&]() {
|
||||
digitalWrite(led, 1);
|
||||
server.send(200, "text/plain", "Hi!, This route is accessible for AP clients only");
|
||||
digitalWrite(led, 0);
|
||||
}
|
||||
)
|
||||
.setFilter(ON_AP_FILTER);
|
||||
|
||||
server.on("/inline", []() {
|
||||
server.send(200, "text/plain", "this works as well");
|
||||
});
|
||||
|
||||
server.onNotFound(handleNotFound);
|
||||
|
||||
server.begin();
|
||||
Serial.println("HTTP server started");
|
||||
}
|
||||
|
||||
void loop(void) {
|
||||
server.handleClient();
|
||||
delay(2); //allow the cpu to switch to other tasks
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
requires_any:
|
||||
- CONFIG_SOC_WIFI_SUPPORTED=y
|
||||
- CONFIG_ESP_WIFI_REMOTE_ENABLED=y
|
||||
@@ -0,0 +1,74 @@
|
||||
#include <WiFi.h>
|
||||
#include <NetworkClient.h>
|
||||
#include <WebServer.h>
|
||||
#include <ESPmDNS.h>
|
||||
|
||||
const char *ssid = "........";
|
||||
const char *password = "........";
|
||||
|
||||
WebServer server(80);
|
||||
|
||||
const int led = 13;
|
||||
|
||||
void handleRoot() {
|
||||
digitalWrite(led, 1);
|
||||
server.send(200, "text/plain", "hello from esp32!");
|
||||
digitalWrite(led, 0);
|
||||
}
|
||||
|
||||
void handleNotFound() {
|
||||
digitalWrite(led, 1);
|
||||
String message = "File Not Found\n\n";
|
||||
message += "URI: ";
|
||||
message += server.uri();
|
||||
message += "\nMethod: ";
|
||||
message += (server.method() == HTTP_GET) ? "GET" : "POST";
|
||||
message += "\nArguments: ";
|
||||
message += server.args();
|
||||
message += "\n";
|
||||
for (uint8_t i = 0; i < server.args(); i++) {
|
||||
message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
|
||||
}
|
||||
server.send(404, "text/plain", message);
|
||||
digitalWrite(led, 0);
|
||||
}
|
||||
|
||||
void setup(void) {
|
||||
pinMode(led, OUTPUT);
|
||||
digitalWrite(led, 0);
|
||||
Serial.begin(115200);
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.begin(ssid, password);
|
||||
Serial.println("");
|
||||
|
||||
// Wait for connection
|
||||
while (WiFi.status() != WL_CONNECTED) {
|
||||
delay(500);
|
||||
Serial.print(".");
|
||||
}
|
||||
Serial.println("");
|
||||
Serial.print("Connected to ");
|
||||
Serial.println(ssid);
|
||||
Serial.print("IP address: ");
|
||||
Serial.println(WiFi.localIP());
|
||||
|
||||
if (MDNS.begin("esp32")) {
|
||||
Serial.println("MDNS responder started");
|
||||
}
|
||||
|
||||
server.on("/", handleRoot);
|
||||
|
||||
server.on("/inline", []() {
|
||||
server.send(200, "text/plain", "this works as well");
|
||||
});
|
||||
|
||||
server.onNotFound(handleNotFound);
|
||||
|
||||
server.begin();
|
||||
Serial.println("HTTP server started");
|
||||
}
|
||||
|
||||
void loop(void) {
|
||||
server.handleClient();
|
||||
delay(2); //allow the cpu to switch to other tasks
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
requires_any:
|
||||
- CONFIG_SOC_WIFI_SUPPORTED=y
|
||||
- CONFIG_ESP_WIFI_REMOTE_ENABLED=y
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
HTTP Advanced Authentication example
|
||||
Created Mar 16, 2017 by Ahmed El-Sharnoby.
|
||||
This example code is in the public domain.
|
||||
*/
|
||||
|
||||
#include <WiFi.h>
|
||||
#include <ESPmDNS.h>
|
||||
#include <ArduinoOTA.h>
|
||||
#include <WebServer.h>
|
||||
|
||||
const char *ssid = "........";
|
||||
const char *password = "........";
|
||||
|
||||
WebServer server(80);
|
||||
|
||||
const char *www_username = "admin";
|
||||
const char *www_password = "esp32";
|
||||
// allows you to set the realm of authentication Default:"Login Required"
|
||||
const char *www_realm = "Custom Auth Realm";
|
||||
// the Content of the HTML response in case of Unautherized Access Default:empty
|
||||
String authFailResponse = "Authentication Failed";
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.begin(ssid, password);
|
||||
if (WiFi.waitForConnectResult() != WL_CONNECTED) {
|
||||
Serial.println("WiFi Connect Failed! Rebooting...");
|
||||
delay(1000);
|
||||
ESP.restart();
|
||||
}
|
||||
ArduinoOTA.begin();
|
||||
|
||||
server.on("/", []() {
|
||||
if (!server.authenticate(www_username, www_password))
|
||||
//Basic Auth Method with Custom realm and Failure Response
|
||||
//return server.requestAuthentication(BASIC_AUTH, www_realm, authFailResponse);
|
||||
//Digest Auth Method with realm="Login Required" and empty Failure Response
|
||||
//return server.requestAuthentication(DIGEST_AUTH);
|
||||
//Digest Auth Method with Custom realm and empty Failure Response
|
||||
//return server.requestAuthentication(DIGEST_AUTH, www_realm);
|
||||
//Digest Auth Method with Custom realm and Failure Response
|
||||
{
|
||||
return server.requestAuthentication(DIGEST_AUTH, www_realm, authFailResponse);
|
||||
}
|
||||
server.send(200, "text/plain", "Login OK");
|
||||
});
|
||||
server.begin();
|
||||
|
||||
Serial.print("Open http://");
|
||||
Serial.print(WiFi.localIP());
|
||||
Serial.println("/ in your browser to see it working");
|
||||
}
|
||||
|
||||
void loop() {
|
||||
ArduinoOTA.handle();
|
||||
server.handleClient();
|
||||
delay(2); //allow the cpu to switch to other tasks
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
requires_any:
|
||||
- CONFIG_SOC_WIFI_SUPPORTED=y
|
||||
- CONFIG_ESP_WIFI_REMOTE_ENABLED=y
|
||||
@@ -0,0 +1,60 @@
|
||||
#include <WiFi.h>
|
||||
#include <ESPmDNS.h>
|
||||
#include <ArduinoOTA.h>
|
||||
#include <WebServer.h>
|
||||
|
||||
const char *ssid = "........";
|
||||
const char *password = "........";
|
||||
|
||||
WebServer server(80);
|
||||
|
||||
typedef struct credentials_t {
|
||||
String username;
|
||||
String password;
|
||||
} credentials_t;
|
||||
|
||||
credentials_t passwdfile[] = {
|
||||
{"admin", "esp32"}, {"fred", "41234123"}, {"charlie", "sdfsd"}, {"alice", "vambdnkuhj"}, {"bob", "svcdbjhws12"},
|
||||
};
|
||||
const size_t N_CREDENTIALS = sizeof(passwdfile) / sizeof(credentials_t);
|
||||
|
||||
String *credentialsHandler(HTTPAuthMethod mode, String username, String params[]) {
|
||||
for (int i = 0; i < N_CREDENTIALS; i++) {
|
||||
if (username == passwdfile[i].username) {
|
||||
return new String(passwdfile[i].password);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.begin(ssid, password);
|
||||
if (WiFi.waitForConnectResult() != WL_CONNECTED) {
|
||||
Serial.println("WiFi Connect Failed! Rebooting...");
|
||||
delay(1000);
|
||||
ESP.restart();
|
||||
}
|
||||
ArduinoOTA.begin();
|
||||
|
||||
server.on("/", []() {
|
||||
if (!server.authenticate(&credentialsHandler)) {
|
||||
server.requestAuthentication();
|
||||
return;
|
||||
}
|
||||
server.send(200, "text/plain", "Login OK");
|
||||
});
|
||||
server.begin();
|
||||
|
||||
Serial.print("Open http://");
|
||||
Serial.print(WiFi.localIP());
|
||||
Serial.println("/ in your browser to see it working");
|
||||
}
|
||||
|
||||
void loop() {
|
||||
ArduinoOTA.handle();
|
||||
server.handleClient();
|
||||
delay(2); //allow the cpu to switch to other tasks
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
requires_any:
|
||||
- CONFIG_SOC_WIFI_SUPPORTED=y
|
||||
- CONFIG_ESP_WIFI_REMOTE_ENABLED=y
|
||||
@@ -0,0 +1,59 @@
|
||||
#include <WiFi.h>
|
||||
#include <ESPmDNS.h>
|
||||
#include <ArduinoOTA.h>
|
||||
#include <WebServer.h>
|
||||
|
||||
const char *ssid = "........";
|
||||
const char *password = "........";
|
||||
|
||||
WebServer server(80);
|
||||
|
||||
typedef struct credentials_t {
|
||||
const char *username;
|
||||
const char *password;
|
||||
} credentials_t;
|
||||
|
||||
credentials_t passwdfile[] = {{"admin", "esp32"}, {"fred", "41234123"}, {"charlie", "sdfsd"}, {"alice", "vambdnkuhj"}, {"bob", "svcdbjhws12"}, {NULL, NULL}};
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.begin(ssid, password);
|
||||
if (WiFi.waitForConnectResult() != WL_CONNECTED) {
|
||||
Serial.println("WiFi Connect Failed! Rebooting...");
|
||||
delay(1000);
|
||||
ESP.restart();
|
||||
}
|
||||
ArduinoOTA.begin();
|
||||
|
||||
server.on("/", []() {
|
||||
if (!server.authenticate([](HTTPAuthMethod mode, String username, String params[]) -> String * {
|
||||
// Scan the password list for the username and return the password if
|
||||
// we find the username.
|
||||
//
|
||||
for (credentials_t *entry = passwdfile; entry->username; entry++) {
|
||||
if (username == entry->username) {
|
||||
return new String(entry->password);
|
||||
};
|
||||
};
|
||||
// we've not found the user in the list.
|
||||
return NULL;
|
||||
})) {
|
||||
server.requestAuthentication();
|
||||
return;
|
||||
}
|
||||
server.send(200, "text/plain", "Login OK");
|
||||
});
|
||||
server.begin();
|
||||
|
||||
Serial.print("Open http://");
|
||||
Serial.print(WiFi.localIP());
|
||||
Serial.println("/ in your browser to see it working");
|
||||
}
|
||||
|
||||
void loop() {
|
||||
ArduinoOTA.handle();
|
||||
server.handleClient();
|
||||
delay(2); //allow the cpu to switch to other tasks
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
requires_any:
|
||||
- CONFIG_SOC_WIFI_SUPPORTED=y
|
||||
- CONFIG_ESP_WIFI_REMOTE_ENABLED=y
|
||||
@@ -0,0 +1,42 @@
|
||||
#include <WiFi.h>
|
||||
#include <ESPmDNS.h>
|
||||
#include <ArduinoOTA.h>
|
||||
#include <WebServer.h>
|
||||
|
||||
const char *ssid = "........";
|
||||
const char *password = "........";
|
||||
|
||||
WebServer server(80);
|
||||
|
||||
const char *www_username = "admin";
|
||||
const char *www_password = "esp32";
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.begin(ssid, password);
|
||||
if (WiFi.waitForConnectResult() != WL_CONNECTED) {
|
||||
Serial.println("WiFi Connect Failed! Rebooting...");
|
||||
delay(1000);
|
||||
ESP.restart();
|
||||
}
|
||||
ArduinoOTA.begin();
|
||||
|
||||
server.on("/", []() {
|
||||
if (!server.authenticate(www_username, www_password)) {
|
||||
return server.requestAuthentication();
|
||||
}
|
||||
server.send(200, "text/plain", "Login OK");
|
||||
});
|
||||
server.begin();
|
||||
|
||||
Serial.print("Open http://");
|
||||
Serial.print(WiFi.localIP());
|
||||
Serial.println("/ in your browser to see it working");
|
||||
}
|
||||
|
||||
void loop() {
|
||||
ArduinoOTA.handle();
|
||||
server.handleClient();
|
||||
delay(2); //allow the cpu to switch to other tasks
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
requires_any:
|
||||
- CONFIG_SOC_WIFI_SUPPORTED=y
|
||||
- CONFIG_ESP_WIFI_REMOTE_ENABLED=y
|
||||
@@ -0,0 +1,72 @@
|
||||
#include <WiFi.h>
|
||||
#include <ESPmDNS.h>
|
||||
#include <ArduinoOTA.h>
|
||||
#include <WebServer.h>
|
||||
|
||||
// Rather than specify the password as plaintext; we
|
||||
// provide it as an (unsalted!) SHA1 hash. This is not
|
||||
// much more secure (SHA1 is past its retirement age,
|
||||
// and long obsolete/insecure) - but it helps a little.
|
||||
|
||||
const char *ssid = "........";
|
||||
const char *password = "........";
|
||||
|
||||
WebServer server(80);
|
||||
|
||||
// Passwords as plaintext - human readable and easily visible in
|
||||
// the sourcecode and in the firmware/binary.
|
||||
const char *www_username = "admin";
|
||||
const char *www_password = "esp32";
|
||||
|
||||
// The sha1 of 'esp32' (without the trailing \0) expressed as 20
|
||||
// bytes of hex. Created by for example 'echo -n esp32 | openssl sha1'
|
||||
// or http://www.sha1-online.com.
|
||||
const char *www_username_hex = "hexadmin";
|
||||
const char *www_password_hex = "8cb124f8c277c16ec0b2ee00569fd151a08e342b";
|
||||
|
||||
// The same; but now expressed as a base64 string (e.g. as commonly used
|
||||
// by webservers). Created by ` echo -n esp32 | openssl sha1 -binary | openssl base64`
|
||||
const char *www_username_base64 = "base64admin";
|
||||
const char *www_password_base64 = "jLEk+MJ3wW7Asu4AVp/RUaCONCs=";
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.begin(ssid, password);
|
||||
if (WiFi.waitForConnectResult() != WL_CONNECTED) {
|
||||
Serial.println("WiFi Connect Failed! Rebooting...");
|
||||
delay(1000);
|
||||
ESP.restart();
|
||||
}
|
||||
ArduinoOTA.begin();
|
||||
|
||||
server.on("/", []() {
|
||||
if (server.authenticate(www_username, www_password)) {
|
||||
server.send(200, "text/plain", "Login against cleartext password OK");
|
||||
return;
|
||||
}
|
||||
if (server.authenticateBasicSHA1(www_username_hex, www_password_hex)) {
|
||||
server.send(200, "text/plain", "Login against HEX of the SHA1 of the password OK");
|
||||
return;
|
||||
}
|
||||
if (server.authenticateBasicSHA1(www_username_base64, www_password_base64)) {
|
||||
server.send(200, "text/plain", "Login against Base64 of the SHA1 of the password OK");
|
||||
return;
|
||||
}
|
||||
Serial.println("No/failed authentication");
|
||||
return server.requestAuthentication();
|
||||
});
|
||||
|
||||
server.begin();
|
||||
|
||||
Serial.print("Open http://");
|
||||
Serial.print(WiFi.localIP());
|
||||
Serial.println("/ in your browser to see it working");
|
||||
}
|
||||
|
||||
void loop() {
|
||||
ArduinoOTA.handle();
|
||||
server.handleClient();
|
||||
delay(2); //allow the cpu to switch to other tasks
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
requires_any:
|
||||
- CONFIG_SOC_WIFI_SUPPORTED=y
|
||||
- CONFIG_ESP_WIFI_REMOTE_ENABLED=y
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
#include <WiFi.h>
|
||||
#include <ESPmDNS.h>
|
||||
#include <ArduinoOTA.h>
|
||||
#include <WebServer.h>
|
||||
#include <SHA1Builder.h>
|
||||
|
||||
// We have two options - we either come in with a bearer
|
||||
// token - i.e. a special header or API token; or we
|
||||
// get a normal HTTP style basic auth prompt.
|
||||
//
|
||||
// To do a bearer fetch - use something like Swagger or with curl:
|
||||
//
|
||||
// curl https://myesp.com/ -H "Authorization: Bearer SecritToken"
|
||||
//
|
||||
// We avoid hardcoding this "SecritToken" into the code by
|
||||
// using a SHA1 instead (which is not particularly secure).
|
||||
|
||||
// Create the secret token SHA1 with:
|
||||
// echo -n SecritToken | openssl sha1
|
||||
|
||||
String secret_token_hex = "d2cce6b472959484a21c3194080c609b8a2c910b";
|
||||
|
||||
// Wifi credentials
|
||||
|
||||
const char *ssid = "........";
|
||||
const char *password = "........";
|
||||
|
||||
WebServer server(80);
|
||||
|
||||
// Rather than specify the admin password as plaintext; we
|
||||
// provide it as an (unsalted!) SHA1 hash. This is not
|
||||
// much more secure (SHA1 is past its retirement age,
|
||||
// and long obsolete/insecure) - but it helps a little.
|
||||
|
||||
// The sha1 of 'esp32' (without the trailing \0) expressed as 20
|
||||
// bytes of hex. Created by for example 'echo -n esp32 | openssl sha1'
|
||||
// or http://www.sha1-online.com.
|
||||
const char *www_username_hex = "admin";
|
||||
const char *www_password_hex = "8cb124f8c277c16ec0b2ee00569fd151a08e342b";
|
||||
|
||||
static unsigned char _bearer[20];
|
||||
|
||||
String *check_bearer_or_auth(HTTPAuthMethod mode, String authReq, String params[]) {
|
||||
// we expect authReq to be "bearer some-secret"
|
||||
String lcAuthReq = authReq;
|
||||
lcAuthReq.toLowerCase();
|
||||
if (mode == OTHER_AUTH && (lcAuthReq.startsWith("bearer "))) {
|
||||
String secret = authReq.substring(7);
|
||||
secret.trim();
|
||||
|
||||
uint8_t sha1[20];
|
||||
SHA1Builder sha_builder;
|
||||
|
||||
sha_builder.begin();
|
||||
sha_builder.add((uint8_t *)secret.c_str(), secret.length());
|
||||
sha_builder.calculate();
|
||||
sha_builder.getBytes(sha1);
|
||||
|
||||
if (memcmp(_bearer, sha1, sizeof(_bearer)) == 0) {
|
||||
Serial.println("Bearer token matches");
|
||||
return new String("anything non null");
|
||||
} else {
|
||||
Serial.println("Bearer token does not match");
|
||||
}
|
||||
} else if (mode == BASIC_AUTH) {
|
||||
bool ret = server.authenticateBasicSHA1(www_username_hex, www_password_hex);
|
||||
if (ret) {
|
||||
Serial.println("Basic auth succeeded");
|
||||
return new String(params[0]);
|
||||
} else {
|
||||
Serial.println("Basic auth failed");
|
||||
}
|
||||
}
|
||||
|
||||
// No auth found
|
||||
return NULL;
|
||||
};
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.begin(ssid, password);
|
||||
if (WiFi.waitForConnectResult() != WL_CONNECTED) {
|
||||
Serial.println("WiFi Connect Failed! Rebooting...");
|
||||
delay(1000);
|
||||
ESP.restart();
|
||||
}
|
||||
ArduinoOTA.begin();
|
||||
|
||||
// Convert token to a convenient binary representation.
|
||||
size_t len = HEXBuilder::hex2bytes(_bearer, sizeof(_bearer), secret_token_hex);
|
||||
if (len != 20) {
|
||||
Serial.println("Bearer token does not look like a valid SHA1 hex string ?!");
|
||||
}
|
||||
|
||||
server.on("/", []() {
|
||||
if (!server.authenticate(&check_bearer_or_auth)) {
|
||||
Serial.println("No/failed authentication");
|
||||
return server.requestAuthentication();
|
||||
}
|
||||
Serial.println("Authentication succeeded");
|
||||
server.send(200, "text/plain", "Login OK");
|
||||
return;
|
||||
});
|
||||
|
||||
server.begin();
|
||||
|
||||
Serial.print("Open http://");
|
||||
Serial.print(WiFi.localIP());
|
||||
Serial.println("/ in your browser to see it working");
|
||||
}
|
||||
|
||||
void loop() {
|
||||
ArduinoOTA.handle();
|
||||
server.handleClient();
|
||||
delay(2); //allow the cpu to switch to other tasks
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
requires_any:
|
||||
- CONFIG_SOC_WIFI_SUPPORTED=y
|
||||
- CONFIG_ESP_WIFI_REMOTE_ENABLED=y
|
||||
@@ -0,0 +1,186 @@
|
||||
/**
|
||||
* Basic example of using Middlewares with WebServer
|
||||
*
|
||||
* Middleware are common request/response processing functions that can be applied globally to all incoming requests or to specific handlers.
|
||||
* They allow for a common processing thus saving memory and space to avoid duplicating code or states on multiple handlers.
|
||||
*
|
||||
* Once the example is flashed (with the correct WiFi credentials), you can test the following scenarios with the listed curl commands:
|
||||
* - CORS Middleware: answers to OPTIONS requests with the specified CORS headers and also add CORS headers to the response when the request has the Origin header
|
||||
* - Logging Middleware: logs the request and response to an output in a curl-like format
|
||||
* - Authentication Middleware: test the authentication with Digest Auth
|
||||
*
|
||||
* You can also add your own Middleware by extending the Middleware class and implementing the run method.
|
||||
* When implementing a Middleware, you can decide when to call the next Middleware in the chain by calling next().
|
||||
*
|
||||
* Middleware are execute in order of addition, the ones attached to the server will be executed first.
|
||||
*/
|
||||
#include <WiFi.h>
|
||||
#include <WebServer.h>
|
||||
#include <Middlewares.h>
|
||||
|
||||
// Your AP WiFi Credentials
|
||||
// ( This is the AP your ESP will broadcast )
|
||||
const char *ap_ssid = "ESP32_Demo";
|
||||
const char *ap_password = "";
|
||||
|
||||
WebServer server(80);
|
||||
|
||||
LoggingMiddleware logger;
|
||||
CorsMiddleware cors;
|
||||
AuthenticationMiddleware auth;
|
||||
|
||||
void setup(void) {
|
||||
Serial.begin(115200);
|
||||
WiFi.softAP(ap_ssid, ap_password);
|
||||
|
||||
Serial.print("IP address: ");
|
||||
Serial.println(WiFi.AP.localIP());
|
||||
|
||||
// curl-like output example:
|
||||
//
|
||||
// > curl -v -X OPTIONS -H "origin: http://192.168.4.1" http://192.168.4.1/
|
||||
//
|
||||
// Connection from 192.168.4.2:51683
|
||||
// > OPTIONS / HTTP/1.1
|
||||
// > Host: 192.168.4.1
|
||||
// > User-Agent: curl/8.10.0
|
||||
// > Accept: */*
|
||||
// > origin: http://192.168.4.1
|
||||
// >
|
||||
// * Processed in 5 ms
|
||||
// < HTTP/1.HTTP/1.1 200 OK
|
||||
// < Content-Type: text/html
|
||||
// < Access-Control-Allow-Origin: http://192.168.4.1
|
||||
// < Access-Control-Allow-Methods: POST,GET,OPTIONS,DELETE
|
||||
// < Access-Control-Allow-Headers: X-Custom-Header
|
||||
// < Access-Control-Allow-Credentials: false
|
||||
// < Access-Control-Max-Age: 600
|
||||
// < Content-Length: 0
|
||||
// < Connection: close
|
||||
// <
|
||||
logger.setOutput(Serial);
|
||||
|
||||
cors.setOrigin("http://192.168.4.1");
|
||||
cors.setMethods("POST,GET,OPTIONS,DELETE");
|
||||
cors.setHeaders("X-Custom-Header");
|
||||
cors.setAllowCredentials(false);
|
||||
cors.setMaxAge(600);
|
||||
|
||||
auth.setUsername("admin");
|
||||
auth.setPassword("admin");
|
||||
auth.setRealm("My Super App");
|
||||
auth.setAuthMethod(DIGEST_AUTH);
|
||||
auth.setAuthFailureMessage("Authentication Failed");
|
||||
|
||||
server.addMiddleware(&logger);
|
||||
server.addMiddleware(&cors);
|
||||
|
||||
// Not authenticated
|
||||
//
|
||||
// Test CORS preflight request with:
|
||||
// > curl -v -X OPTIONS -H "origin: http://192.168.4.1" http://192.168.4.1/
|
||||
//
|
||||
// Test cross-domain request with:
|
||||
// > curl -v -X GET -H "origin: http://192.168.4.1" http://192.168.4.1/
|
||||
//
|
||||
server.on("/", []() {
|
||||
server.send(200, "text/plain", "Home");
|
||||
});
|
||||
|
||||
// Authenticated
|
||||
//
|
||||
// > curl -v -X GET -H "origin: http://192.168.4.1" http://192.168.4.1/protected
|
||||
//
|
||||
// Outputs:
|
||||
//
|
||||
// * Connection from 192.168.4.2:51750
|
||||
// > GET /protected HTTP/1.1
|
||||
// > Host: 192.168.4.1
|
||||
// > User-Agent: curl/8.10.0
|
||||
// > Accept: */*
|
||||
// > origin: http://192.168.4.1
|
||||
// >
|
||||
// * Processed in 7 ms
|
||||
// < HTTP/1.HTTP/1.1 401 Unauthorized
|
||||
// < Content-Type: text/html
|
||||
// < Access-Control-Allow-Origin: http://192.168.4.1
|
||||
// < Access-Control-Allow-Methods: POST,GET,OPTIONS,DELETE
|
||||
// < Access-Control-Allow-Headers: X-Custom-Header
|
||||
// < Access-Control-Allow-Credentials: false
|
||||
// < Access-Control-Max-Age: 600
|
||||
// < WWW-Authenticate: Digest realm="My Super App", qop="auth", nonce="ac388a64184e3e102aae6fff1c9e8d76", opaque="e7d158f2b54d25328142d118ff0f932d"
|
||||
// < Content-Length: 21
|
||||
// < Connection: close
|
||||
// <
|
||||
//
|
||||
// > curl -v -X GET -H "origin: http://192.168.4.1" --digest -u admin:admin http://192.168.4.1/protected
|
||||
//
|
||||
// Outputs:
|
||||
//
|
||||
// * Connection from 192.168.4.2:53662
|
||||
// > GET /protected HTTP/1.1
|
||||
// > Authorization: Digest username="admin", realm="My Super App", nonce="db9e6824eb2a13bc7b2bf8f3c43db896", uri="/protected", cnonce="NTliZDZiNTcwODM2MzAyY2JjMDBmZGJmNzFiY2ZmNzk=", nc=00000001, qop=auth, response="6ebd145ba0d3496a4a73f5ae79ff5264", opaque="23d739c22810282ff820538cba98bda4"
|
||||
// > Host: 192.168.4.1
|
||||
// > User-Agent: curl/8.10.0
|
||||
// > Accept: */*
|
||||
// > origin: http://192.168.4.1
|
||||
// >
|
||||
// Request handling...
|
||||
// * Processed in 7 ms
|
||||
// < HTTP/1.HTTP/1.1 200 OK
|
||||
// < Content-Type: text/plain
|
||||
// < Access-Control-Allow-Origin: http://192.168.4.1
|
||||
// < Access-Control-Allow-Methods: POST,GET,OPTIONS,DELETE
|
||||
// < Access-Control-Allow-Headers: X-Custom-Header
|
||||
// < Access-Control-Allow-Credentials: false
|
||||
// < Access-Control-Max-Age: 600
|
||||
// < Content-Length: 9
|
||||
// < Connection: close
|
||||
// <
|
||||
server
|
||||
.on(
|
||||
"/protected",
|
||||
[]() {
|
||||
Serial.println("Request handling...");
|
||||
server.send(200, "text/plain", "Protected");
|
||||
}
|
||||
)
|
||||
.addMiddleware(&auth);
|
||||
|
||||
// Not found is also handled by global middleware
|
||||
//
|
||||
// curl -v -X GET -H "origin: http://192.168.4.1" http://192.168.4.1/inexsting
|
||||
//
|
||||
// Outputs:
|
||||
//
|
||||
// * Connection from 192.168.4.2:53683
|
||||
// > GET /inexsting HTTP/1.1
|
||||
// > Host: 192.168.4.1
|
||||
// > User-Agent: curl/8.10.0
|
||||
// > Accept: */*
|
||||
// > origin: http://192.168.4.1
|
||||
// >
|
||||
// * Processed in 16 ms
|
||||
// < HTTP/1.HTTP/1.1 404 Not Found
|
||||
// < Content-Type: text/plain
|
||||
// < Access-Control-Allow-Origin: http://192.168.4.1
|
||||
// < Access-Control-Allow-Methods: POST,GET,OPTIONS,DELETE
|
||||
// < Access-Control-Allow-Headers: X-Custom-Header
|
||||
// < Access-Control-Allow-Credentials: false
|
||||
// < Access-Control-Max-Age: 600
|
||||
// < Content-Length: 14
|
||||
// < Connection: close
|
||||
// <
|
||||
server.onNotFound([]() {
|
||||
server.send(404, "text/plain", "Page not found");
|
||||
});
|
||||
|
||||
server.collectAllHeaders();
|
||||
server.begin();
|
||||
Serial.println("HTTP server started");
|
||||
}
|
||||
|
||||
void loop(void) {
|
||||
server.handleClient();
|
||||
delay(2); //allow the cpu to switch to other tasks
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
requires:
|
||||
- CONFIG_SOC_WIFI_SUPPORTED=y
|
||||
@@ -0,0 +1,141 @@
|
||||
#include <WiFi.h>
|
||||
#include <NetworkClient.h>
|
||||
#include <WebServer.h>
|
||||
#include <ESPmDNS.h>
|
||||
|
||||
const char *ssid = "WiFi_SSID";
|
||||
const char *password = "WiFi_Password";
|
||||
const char *apssid = "ESP32";
|
||||
|
||||
WebServer *server0, *server1, *server2;
|
||||
|
||||
#ifdef LED_BUILTIN
|
||||
const int led = LED_BUILTIN;
|
||||
#else
|
||||
const int led = 13;
|
||||
#endif
|
||||
|
||||
void handleRoot(WebServer *server, const char *content) {
|
||||
digitalWrite(led, 1);
|
||||
server->send(200, "text/plain", content);
|
||||
digitalWrite(led, 0);
|
||||
}
|
||||
|
||||
void handleRoot0() {
|
||||
handleRoot(server0, "Hello from server0 who listens on both WLAN and own Soft AP");
|
||||
}
|
||||
|
||||
void handleRoot1() {
|
||||
handleRoot(server1, "Hello from server1 who listens only on WLAN");
|
||||
}
|
||||
|
||||
void handleRoot2() {
|
||||
handleRoot(server2, "Hello from server2 who listens only on own Soft AP");
|
||||
}
|
||||
|
||||
void handleNotFound(WebServer *server) {
|
||||
digitalWrite(led, 1);
|
||||
String message = "File Not Found\n\n";
|
||||
message += "URI: ";
|
||||
message += server->uri();
|
||||
message += "\nMethod: ";
|
||||
message += (server->method() == HTTP_GET) ? "GET" : "POST";
|
||||
message += "\nArguments: ";
|
||||
message += server->args();
|
||||
message += "\n";
|
||||
for (uint8_t i = 0; i < server->args(); i++) {
|
||||
message += " " + server->argName(i) + ": " + server->arg(i) + "\n";
|
||||
}
|
||||
server->send(404, "text/plain", message);
|
||||
digitalWrite(led, 0);
|
||||
}
|
||||
|
||||
void handleNotFound0() {
|
||||
handleNotFound(server0);
|
||||
}
|
||||
|
||||
void handleNotFound1() {
|
||||
handleNotFound(server1);
|
||||
}
|
||||
|
||||
void handleNotFound2() {
|
||||
handleNotFound(server2);
|
||||
}
|
||||
|
||||
void setup(void) {
|
||||
pinMode(led, OUTPUT);
|
||||
digitalWrite(led, 0);
|
||||
Serial.begin(115200);
|
||||
|
||||
Serial.println("Multi-homed Servers example starting");
|
||||
delay(1000);
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.begin(ssid, password);
|
||||
Serial.print("Connecting ");
|
||||
|
||||
// Wait for connection
|
||||
while (WiFi.status() != WL_CONNECTED) {
|
||||
delay(500);
|
||||
Serial.print(".");
|
||||
}
|
||||
Serial.println("");
|
||||
Serial.print("Connected to \"");
|
||||
Serial.print(ssid);
|
||||
Serial.print("\", IP address: \"");
|
||||
Serial.println(WiFi.localIP());
|
||||
if (!WiFi.softAP(apssid)) {
|
||||
Serial.println("failed to start softAP");
|
||||
for (;;) {
|
||||
digitalWrite(led, 1);
|
||||
delay(100);
|
||||
digitalWrite(led, 0);
|
||||
delay(100);
|
||||
}
|
||||
}
|
||||
Serial.print("Soft AP SSID: \"");
|
||||
Serial.print(apssid);
|
||||
Serial.print("\", IP address: ");
|
||||
Serial.println(WiFi.softAPIP());
|
||||
|
||||
if (MDNS.begin("esp32")) {
|
||||
Serial.println("MDNS responder started");
|
||||
}
|
||||
|
||||
server0 = new WebServer(8080);
|
||||
server1 = new WebServer(WiFi.localIP(), 8081);
|
||||
server2 = new WebServer(WiFi.softAPIP(), 8081);
|
||||
|
||||
server0->on("/", handleRoot0);
|
||||
server1->on("/", handleRoot1);
|
||||
server2->on("/", handleRoot2);
|
||||
|
||||
server0->onNotFound(handleNotFound0);
|
||||
server1->onNotFound(handleNotFound1);
|
||||
server2->onNotFound(handleNotFound2);
|
||||
|
||||
server0->begin();
|
||||
Serial.println("HTTP server0 started");
|
||||
server1->begin();
|
||||
Serial.println("HTTP server1 started");
|
||||
server2->begin();
|
||||
Serial.println("HTTP server2 started");
|
||||
|
||||
Serial.printf("SSID: %s\n\thttp://", ssid);
|
||||
Serial.print(WiFi.localIP());
|
||||
Serial.print(":8080\n\thttp://");
|
||||
Serial.print(WiFi.localIP());
|
||||
Serial.println(":8081");
|
||||
Serial.printf("SSID: %s\n\thttp://", apssid);
|
||||
Serial.print(WiFi.softAPIP());
|
||||
Serial.print(":8080\n\thttp://");
|
||||
Serial.print(WiFi.softAPIP());
|
||||
Serial.println(":8081");
|
||||
Serial.printf("Any of the above SSIDs\n\thttp://esp32.local:8080\n\thttp://esp32.local:8081\n");
|
||||
}
|
||||
|
||||
void loop(void) {
|
||||
server0->handleClient();
|
||||
server1->handleClient();
|
||||
server2->handleClient();
|
||||
delay(2); //allow the cpu to switch to other tasks
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
# Multi Homed Servers
|
||||
|
||||
This example tests support for multi-homed servers, i.e. a distinct web servers on distinct IP interface.
|
||||
|
||||
It only tests the case n=2 because on a basic ESP32 device, we only have two IP interfaces, namely the Wi-Fi station interfaces and the Wi-Fi soft AP interface.
|
||||
For this to work, the WebServer and the NetworkServer classes must correctly handle the case where an IP address is passed to their relevant constructor.
|
||||
It also requires WebServer to work with multiple, simultaneous instances.
|
||||
|
||||
Testing the WebServer and the NetworkServer constructors was the primary purpose of this script.
|
||||
The part of WebServer used by this sketch does seem to work with multiple, simultaneous instances.
|
||||
However there is much functionality in WebServer that is not tested here. It may all be well, but that is not proven here.
|
||||
|
||||
This sketch starts the mDNS server, as did HelloServer, and it resolves esp32.local on both interfaces, but was not otherwise tested.
|
||||
This script also tests that a server not bound to a specific IP address still works.
|
||||
|
||||
We create three, simultaneous web servers, one specific to each interface and one that listens on both:
|
||||
|
||||
| name | IP Address | Port |
|
||||
| ---- | ---------- | ---- |
|
||||
| server0 | INADDR_ANY | 8080 |
|
||||
| server1 | station address | 8081 |
|
||||
| server2 | soft AP address | 8081 |
|
||||
|
||||
The expected responses to a browser's requests are as follows:
|
||||
|
||||
#### 1. The Client connected to the same WLAN as the station:
|
||||
|
||||
| Request URL | Response |
|
||||
| ----------- | -------- |
|
||||
| [http://stationaddress:8080](http://stationaddress:8080) | Hello from server0 who listens on both WLAN and own Soft AP |
|
||||
| [http://stationaddress:8081](http://stationaddress:8081) | Hello from server1 who listens only on WLAN |
|
||||
|
||||
#### 2. The Client is connected to the soft AP:
|
||||
|
||||
| Request URL | Response |
|
||||
| ----------- | -------- |
|
||||
| [http://softAPaddress:8080](http://softAPaddress:8080) | Hello from server0 who listens on both WLAN and own Soft AP |
|
||||
| [http://softAPaddress:8081](http://softAPaddress:8081) | Hello from server2 who listens only on own Soft AP |
|
||||
|
||||
#### 3. The Client is connect to either WLAN or SoftAP:
|
||||
|
||||
| Request URL | Response |
|
||||
| ----------- | -------- |
|
||||
| [http://esp32.local:8080](http://esp32.local:8080) | Hello from server0 who listens on both WLAN and own Soft AP |
|
||||
| [http://esp32.local:8081](http://esp32.local:8081) | Hello from server1 who listens only on WLAN |
|
||||
|
||||
MultiHomedServers was originally based on HelloServer.
|
||||
|
||||
# Supported Targets
|
||||
|
||||
Currently, this example supports the following targets.
|
||||
|
||||
| Supported Targets | ESP32 | ESP32-S2 | ESP32-C3 | ESP32-S3 |
|
||||
| ----------------- | ----- | -------- | -------- | -------- |
|
||||
|
||||
## How to Use Example
|
||||
|
||||
Change the SSID and password in the example to your Wi-Fi and flash the example.
|
||||
Open a serial terminal and the example will write the exact addresses with used IP addresses you can use to test the servers.
|
||||
|
||||
* How to install the Arduino IDE: [Install Arduino IDE](https://github.com/espressif/arduino-esp32/tree/master/docs/arduino-ide).
|
||||
|
||||
#### Using Arduino IDE
|
||||
|
||||
To get more information about the Espressif boards see [Espressif Development Kits](https://www.espressif.com/en/products/devkits).
|
||||
|
||||
* Before Compile/Verify, select the correct board: `Tools -> Board`.
|
||||
* Select the COM port: `Tools -> Port: xxx` where the `xxx` is the detected COM port.
|
||||
|
||||
## Example Log Output
|
||||
|
||||
```
|
||||
Multi-homed Servers example starting
|
||||
Connecting ...
|
||||
Connected to "WiFi_SSID", IP address: "192.168.42.24
|
||||
Soft AP SSID: "ESP32", IP address: 192.168.4.1
|
||||
MDNS responder started
|
||||
SSID: WiFi_SSID
|
||||
http://192.168.42.24:8080
|
||||
http://192.168.42.24:8081
|
||||
SSID: ESP32
|
||||
http://192.168.4.1:8080
|
||||
http://192.168.4.1:8081
|
||||
Any of the above SSIDs
|
||||
http://esp32.local:8080
|
||||
http://esp32.local:8081
|
||||
HTTP server0 started
|
||||
HTTP server1 started
|
||||
HTTP server2 started
|
||||
```
|
||||
|
||||
## Known issues
|
||||
|
||||
`http://esp32.local` Does not work on some Android phones
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
***Important: Make sure you are using a good quality USB cable and that you have a reliable power source***
|
||||
|
||||
## Contribute
|
||||
|
||||
To know how to contribute to this project, see [How to contribute.](https://github.com/espressif/arduino-esp32/blob/master/CONTRIBUTING.rst)
|
||||
|
||||
If you have any **feedback** or **issue** to report on this example/library, please open an issue or fix it by creating a new PR. Contributions are more than welcome!
|
||||
|
||||
Before creating a new issue, be sure to try Troubleshooting and check if the same issue was already created by someone else.
|
||||
|
||||
## Resources
|
||||
|
||||
* Official ESP32 Forum: [Link](https://esp32.com)
|
||||
* Arduino-ESP32 Official Repository: [espressif/arduino-esp32](https://github.com/espressif/arduino-esp32)
|
||||
* ESP32 Datasheet: [Link to datasheet](https://www.espressif.com/sites/default/files/documentation/esp32_datasheet_en.pdf)
|
||||
* ESP32-S2 Datasheet: [Link to datasheet](https://www.espressif.com/sites/default/files/documentation/esp32-s2_datasheet_en.pdf)
|
||||
* ESP32-C3 Datasheet: [Link to datasheet](https://www.espressif.com/sites/default/files/documentation/esp32-c3_datasheet_en.pdf)
|
||||
* Official ESP-IDF documentation: [ESP-IDF](https://idf.espressif.com)
|
||||
@@ -0,0 +1,3 @@
|
||||
requires_any:
|
||||
- CONFIG_SOC_WIFI_SUPPORTED=y
|
||||
- CONFIG_ESP_WIFI_REMOTE_ENABLED=y
|
||||
@@ -0,0 +1,57 @@
|
||||
#include <WiFi.h>
|
||||
#include <NetworkClient.h>
|
||||
#include <WebServer.h>
|
||||
#include <ESPmDNS.h>
|
||||
|
||||
#include <uri/UriBraces.h>
|
||||
#include <uri/UriRegex.h>
|
||||
|
||||
const char *ssid = "........";
|
||||
const char *password = "........";
|
||||
|
||||
WebServer server(80);
|
||||
|
||||
void setup(void) {
|
||||
Serial.begin(9600);
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.begin(ssid, password);
|
||||
Serial.println("");
|
||||
|
||||
// Wait for connection
|
||||
while (WiFi.status() != WL_CONNECTED) {
|
||||
delay(500);
|
||||
Serial.print(".");
|
||||
}
|
||||
Serial.println("");
|
||||
Serial.print("Connected to ");
|
||||
Serial.println(ssid);
|
||||
Serial.print("IP address: ");
|
||||
Serial.println(WiFi.localIP());
|
||||
|
||||
if (MDNS.begin("esp32")) {
|
||||
Serial.println("MDNS responder started");
|
||||
}
|
||||
|
||||
server.on(F("/"), []() {
|
||||
server.send(200, "text/plain", "hello from esp32!");
|
||||
});
|
||||
|
||||
server.on(UriBraces("/users/{}"), []() {
|
||||
String user = server.pathArg(0);
|
||||
server.send(200, "text/plain", "User: '" + user + "'");
|
||||
});
|
||||
|
||||
server.on(UriRegex("^\\/users\\/([0-9]+)\\/devices\\/([0-9]+)$"), []() {
|
||||
String user = server.pathArg(0);
|
||||
String device = server.pathArg(1);
|
||||
server.send(200, "text/plain", "User: '" + user + "' and Device: '" + device + "'");
|
||||
});
|
||||
|
||||
server.begin();
|
||||
Serial.println("HTTP server started");
|
||||
}
|
||||
|
||||
void loop(void) {
|
||||
server.handleClient();
|
||||
delay(2); //allow the cpu to switch to other tasks
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
fqbn_append: PartitionScheme=huge_app
|
||||
|
||||
requires_any:
|
||||
- CONFIG_SOC_WIFI_SUPPORTED=y
|
||||
- CONFIG_ESP_WIFI_REMOTE_ENABLED=y
|
||||
@@ -0,0 +1,320 @@
|
||||
/*
|
||||
SDWebServer - Example WebServer with SD Card backend for esp8266
|
||||
|
||||
Copyright (c) 2015 Hristo Gochkov. All rights reserved.
|
||||
This file is part of the WebServer library for Arduino environment.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
Have a FAT Formatted SD Card connected to the SPI port of the ESP8266
|
||||
The web root is the SD Card root folder
|
||||
File extensions with more than 3 characters are not supported by the SD Library
|
||||
File Names longer than 8 characters will be truncated by the SD library, so keep filenames shorter
|
||||
index.htm is the default index (works on subfolders as well)
|
||||
|
||||
upload the contents of SdRoot to the root of the SDcard and access the editor by going to http://esp8266sd.local/edit
|
||||
To retrieve the contents of SDcard, visit http://esp32sd.local/list?dir=/
|
||||
dir is the argument that needs to be passed to the function PrintDirectory via HTTP Get request.
|
||||
|
||||
*/
|
||||
#include <WiFi.h>
|
||||
#include <NetworkClient.h>
|
||||
#include <WebServer.h>
|
||||
#include <ESPmDNS.h>
|
||||
#include <SPI.h>
|
||||
#include <SD.h>
|
||||
|
||||
#define DBG_OUTPUT_PORT Serial
|
||||
|
||||
const char *ssid = "**********";
|
||||
const char *password = "**********";
|
||||
const char *host = "esp32sd";
|
||||
|
||||
WebServer server(80);
|
||||
|
||||
static bool hasSD = false;
|
||||
File uploadFile;
|
||||
|
||||
void returnOK() {
|
||||
server.send(200, "text/plain", "");
|
||||
}
|
||||
|
||||
void returnFail(String msg) {
|
||||
server.send(500, "text/plain", msg + "\r\n");
|
||||
}
|
||||
|
||||
bool loadFromSdCard(String path) {
|
||||
String dataType = "text/plain";
|
||||
if (path.endsWith("/")) {
|
||||
path += "index.htm";
|
||||
}
|
||||
|
||||
if (path.endsWith(".src")) {
|
||||
path = path.substring(0, path.lastIndexOf("."));
|
||||
} else if (path.endsWith(".htm")) {
|
||||
dataType = "text/html";
|
||||
} else if (path.endsWith(".css")) {
|
||||
dataType = "text/css";
|
||||
} else if (path.endsWith(".js")) {
|
||||
dataType = "application/javascript";
|
||||
} else if (path.endsWith(".png")) {
|
||||
dataType = "image/png";
|
||||
} else if (path.endsWith(".gif")) {
|
||||
dataType = "image/gif";
|
||||
} else if (path.endsWith(".jpg")) {
|
||||
dataType = "image/jpeg";
|
||||
} else if (path.endsWith(".ico")) {
|
||||
dataType = "image/x-icon";
|
||||
} else if (path.endsWith(".xml")) {
|
||||
dataType = "text/xml";
|
||||
} else if (path.endsWith(".pdf")) {
|
||||
dataType = "application/pdf";
|
||||
} else if (path.endsWith(".zip")) {
|
||||
dataType = "application/zip";
|
||||
}
|
||||
|
||||
File dataFile = SD.open(path.c_str());
|
||||
if (dataFile.isDirectory()) {
|
||||
path += "/index.htm";
|
||||
dataType = "text/html";
|
||||
dataFile = SD.open(path.c_str());
|
||||
}
|
||||
|
||||
if (!dataFile) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (server.hasArg("download")) {
|
||||
dataType = "application/octet-stream";
|
||||
}
|
||||
|
||||
if (server.streamFile(dataFile, dataType) != dataFile.size()) {
|
||||
DBG_OUTPUT_PORT.println("Sent less data than expected!");
|
||||
}
|
||||
|
||||
dataFile.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
void handleFileUpload() {
|
||||
if (server.uri() != "/edit") {
|
||||
return;
|
||||
}
|
||||
HTTPUpload &upload = server.upload();
|
||||
if (upload.status == UPLOAD_FILE_START) {
|
||||
if (SD.exists((char *)upload.filename.c_str())) {
|
||||
SD.remove((char *)upload.filename.c_str());
|
||||
}
|
||||
uploadFile = SD.open(upload.filename.c_str(), FILE_WRITE);
|
||||
DBG_OUTPUT_PORT.print("Upload: START, filename: ");
|
||||
DBG_OUTPUT_PORT.println(upload.filename);
|
||||
} else if (upload.status == UPLOAD_FILE_WRITE) {
|
||||
if (uploadFile) {
|
||||
uploadFile.write(upload.buf, upload.currentSize);
|
||||
}
|
||||
DBG_OUTPUT_PORT.print("Upload: WRITE, Bytes: ");
|
||||
DBG_OUTPUT_PORT.println(upload.currentSize);
|
||||
} else if (upload.status == UPLOAD_FILE_END) {
|
||||
if (uploadFile) {
|
||||
uploadFile.close();
|
||||
}
|
||||
DBG_OUTPUT_PORT.print("Upload: END, Size: ");
|
||||
DBG_OUTPUT_PORT.println(upload.totalSize);
|
||||
}
|
||||
}
|
||||
|
||||
void deleteRecursive(String path) {
|
||||
File file = SD.open((char *)path.c_str());
|
||||
if (!file.isDirectory()) {
|
||||
file.close();
|
||||
SD.remove((char *)path.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
file.rewindDirectory();
|
||||
while (true) {
|
||||
File entry = file.openNextFile();
|
||||
if (!entry) {
|
||||
break;
|
||||
}
|
||||
String entryPath = path + "/" + entry.name();
|
||||
if (entry.isDirectory()) {
|
||||
entry.close();
|
||||
deleteRecursive(entryPath);
|
||||
} else {
|
||||
entry.close();
|
||||
SD.remove((char *)entryPath.c_str());
|
||||
}
|
||||
yield();
|
||||
}
|
||||
|
||||
SD.rmdir((char *)path.c_str());
|
||||
file.close();
|
||||
}
|
||||
|
||||
void handleDelete() {
|
||||
if (server.args() == 0) {
|
||||
return returnFail("BAD ARGS");
|
||||
}
|
||||
String path = server.arg(0);
|
||||
if (path == "/" || !SD.exists((char *)path.c_str())) {
|
||||
returnFail("BAD PATH");
|
||||
return;
|
||||
}
|
||||
deleteRecursive(path);
|
||||
returnOK();
|
||||
}
|
||||
|
||||
void handleCreate() {
|
||||
if (server.args() == 0) {
|
||||
return returnFail("BAD ARGS");
|
||||
}
|
||||
String path = server.arg(0);
|
||||
if (path == "/" || SD.exists((char *)path.c_str())) {
|
||||
returnFail("BAD PATH");
|
||||
return;
|
||||
}
|
||||
|
||||
if (path.indexOf('.') > 0) {
|
||||
File file = SD.open((char *)path.c_str(), FILE_WRITE);
|
||||
if (file) {
|
||||
file.write(0);
|
||||
file.close();
|
||||
}
|
||||
} else {
|
||||
SD.mkdir((char *)path.c_str());
|
||||
}
|
||||
returnOK();
|
||||
}
|
||||
|
||||
void printDirectory() {
|
||||
if (!server.hasArg("dir")) {
|
||||
return returnFail("BAD ARGS");
|
||||
}
|
||||
String path = server.arg("dir");
|
||||
if (path != "/" && !SD.exists((char *)path.c_str())) {
|
||||
return returnFail("BAD PATH");
|
||||
}
|
||||
File dir = SD.open((char *)path.c_str());
|
||||
path = String();
|
||||
if (!dir.isDirectory()) {
|
||||
dir.close();
|
||||
return returnFail("NOT DIR");
|
||||
}
|
||||
dir.rewindDirectory();
|
||||
server.setContentLength(CONTENT_LENGTH_UNKNOWN);
|
||||
server.send(200, "text/json", "");
|
||||
|
||||
server.sendContent("[");
|
||||
for (int cnt = 0; true; ++cnt) {
|
||||
File entry = dir.openNextFile();
|
||||
if (!entry) {
|
||||
break;
|
||||
}
|
||||
|
||||
String output;
|
||||
if (cnt > 0) {
|
||||
output = ',';
|
||||
}
|
||||
|
||||
output += "{\"type\":\"";
|
||||
output += (entry.isDirectory()) ? "dir" : "file";
|
||||
output += "\",\"name\":\"";
|
||||
output += entry.path();
|
||||
output += "\"";
|
||||
output += "}";
|
||||
server.sendContent(output);
|
||||
entry.close();
|
||||
}
|
||||
server.sendContent("]");
|
||||
dir.close();
|
||||
}
|
||||
|
||||
void handleNotFound() {
|
||||
if (hasSD && loadFromSdCard(server.uri())) {
|
||||
return;
|
||||
}
|
||||
String message = "SDCARD Not Detected\n\n";
|
||||
message += "URI: ";
|
||||
message += server.uri();
|
||||
message += "\nMethod: ";
|
||||
message += (server.method() == HTTP_GET) ? "GET" : "POST";
|
||||
message += "\nArguments: ";
|
||||
message += server.args();
|
||||
message += "\n";
|
||||
for (uint8_t i = 0; i < server.args(); i++) {
|
||||
message += " NAME:" + server.argName(i) + "\n VALUE:" + server.arg(i) + "\n";
|
||||
}
|
||||
server.send(404, "text/plain", message);
|
||||
DBG_OUTPUT_PORT.print(message);
|
||||
}
|
||||
|
||||
void setup(void) {
|
||||
DBG_OUTPUT_PORT.begin(115200);
|
||||
DBG_OUTPUT_PORT.setDebugOutput(true);
|
||||
DBG_OUTPUT_PORT.print("\n");
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.begin(ssid, password);
|
||||
DBG_OUTPUT_PORT.print("Connecting to ");
|
||||
DBG_OUTPUT_PORT.println(ssid);
|
||||
|
||||
// Wait for connection
|
||||
uint8_t i = 0;
|
||||
while (WiFi.status() != WL_CONNECTED && i++ < 20) { //wait 10 seconds
|
||||
delay(500);
|
||||
}
|
||||
if (i == 21) {
|
||||
DBG_OUTPUT_PORT.print("Could not connect to");
|
||||
DBG_OUTPUT_PORT.println(ssid);
|
||||
while (1) {
|
||||
delay(500);
|
||||
}
|
||||
}
|
||||
DBG_OUTPUT_PORT.print("Connected! IP address: ");
|
||||
DBG_OUTPUT_PORT.println(WiFi.localIP());
|
||||
|
||||
if (MDNS.begin(host)) {
|
||||
MDNS.addService("http", "tcp", 80);
|
||||
DBG_OUTPUT_PORT.println("MDNS responder started");
|
||||
DBG_OUTPUT_PORT.print("You can now connect to http://");
|
||||
DBG_OUTPUT_PORT.print(host);
|
||||
DBG_OUTPUT_PORT.println(".local");
|
||||
}
|
||||
|
||||
server.on("/list", HTTP_GET, printDirectory);
|
||||
server.on("/edit", HTTP_DELETE, handleDelete);
|
||||
server.on("/edit", HTTP_PUT, handleCreate);
|
||||
server.on(
|
||||
"/edit", HTTP_POST,
|
||||
[]() {
|
||||
returnOK();
|
||||
},
|
||||
handleFileUpload
|
||||
);
|
||||
server.onNotFound(handleNotFound);
|
||||
|
||||
server.begin();
|
||||
DBG_OUTPUT_PORT.println("HTTP server started");
|
||||
|
||||
if (SD.begin(SS)) {
|
||||
DBG_OUTPUT_PORT.println("SD Card initialized.");
|
||||
hasSD = true;
|
||||
}
|
||||
}
|
||||
|
||||
void loop(void) {
|
||||
server.handleClient();
|
||||
delay(2); //allow the cpu to switch to other tasks
|
||||
}
|
||||
@@ -0,0 +1,674 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>SD Editor</title>
|
||||
<style type="text/css" media="screen">
|
||||
.contextMenu {
|
||||
z-index: 300;
|
||||
position: absolute;
|
||||
left: 5px;
|
||||
border: 1px solid #444;
|
||||
background-color: #F5F5F5;
|
||||
display: none;
|
||||
box-shadow: 0 0 10px rgba( 0, 0, 0, .4 );
|
||||
font-size: 12px;
|
||||
font-family: sans-serif;
|
||||
font-weight:bold;
|
||||
}
|
||||
.contextMenu ul {
|
||||
list-style: none;
|
||||
top: 0;
|
||||
left: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.contextMenu li {
|
||||
position: relative;
|
||||
min-width: 60px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.contextMenu span {
|
||||
color: #444;
|
||||
display: inline-block;
|
||||
padding: 6px;
|
||||
}
|
||||
.contextMenu li:hover { background: #444; }
|
||||
.contextMenu li:hover span { color: #EEE; }
|
||||
|
||||
.css-treeview ul, .css-treeview li {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.css-treeview input {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.css-treeview {
|
||||
font: normal 11px Verdana, Arial, Sans-serif;
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.css-treeview span {
|
||||
color: #00f;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.css-treeview span:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.css-treeview input + label + ul {
|
||||
margin: 0 0 0 22px;
|
||||
}
|
||||
|
||||
.css-treeview input ~ ul {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.css-treeview label, .css-treeview label::before {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.css-treeview input:disabled + label {
|
||||
cursor: default;
|
||||
opacity: .6;
|
||||
}
|
||||
|
||||
.css-treeview input:checked:not(:disabled) ~ ul {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.css-treeview label, .css-treeview label::before {
|
||||
background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAACgCAYAAAAFOewUAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAApxJREFUeNrslM1u00AQgGdthyalFFOK+ClIIKQKyqUVQvTEE3DmAhLwAhU8QZoH4A2Q2gMSFace4MCtJ8SPBFwAkRuiHKpA6sRN/Lu7zG5i14kctaUqRGhGXnu9O/Pt7MzsMiklvF+9t2kWTDvyIrAsA0aKRRi1T0C/hJ4LUbt5/8rNpWVlp8RSr9J40b48fxFaTQ9+ft8EZ6MJYb0Ok+dnYGpmPgXwKIAvLx8vYXc5GdMAQJgQEkpjRTh36TS2U+DWW/D17WuYgm8pwJyY1npZsZKOxImOV1I/h4+O6vEg5GCZBpgmA6hX8wHKUHDRBXQYicQ4rlc3Tf0VMs8DHBS864F2YFspjgUYjKX/Az3gsdQd2eeBHwmdGWXHcgBGSkZXOXohcEXebRoQcAgjqediNY+AVyu3Z3sAKqfKoGMsewBeEIOPgQxxPJIjcGH6qtL/0AdADzKGnuuD+2tLK7Q8DhHHbOBW+KEzcHLuYc82MkEUekLiwuvVH+guQBQzOG4XdAb8EOcRcqQvDkY2iCLuxECJ43JobMXoutqGgDa2T7UqLKwt9KRyuxKVByqVXXqIoCCUCAqhUOioTWC7G4TQEOD0APy2/7G2Xpu1J4+lxeQ4TXBbITDpoVelRN/BVFbwu5oMMJUBhoXy5tmdRcMwymP2OLQaLjx9/vnBo6V3K6izATmSnMa0Dq7ferIohJhr1p01zrlz49rZF4OMs8JkX23vVQzYp+wbYGV/KpXKjvspl8tsIKCrMNAYFxj2GKS5ZWxg4ewKsJfaGMIY5KXqPz8LBBj6+yDvVP79+yDp/9F9oIx3OisHWwe7Oal0HxCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgwD8E/BZgAP0qhKj3rXO7AAAAAElFTkSuQmCC") no-repeat;
|
||||
}
|
||||
|
||||
.css-treeview label, .css-treeview span, .css-treeview label::before {
|
||||
display: inline-block;
|
||||
height: 16px;
|
||||
line-height: 16px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.css-treeview label {
|
||||
background-position: 18px 0;
|
||||
}
|
||||
|
||||
.css-treeview label::before {
|
||||
content: "";
|
||||
width: 16px;
|
||||
margin: 0 22px 0 0;
|
||||
vertical-align: middle;
|
||||
background-position: 0 -32px;
|
||||
}
|
||||
|
||||
.css-treeview input:checked + label::before {
|
||||
background-position: 0 -16px;
|
||||
}
|
||||
|
||||
/* webkit adjacent element selector bugfix */
|
||||
@media screen and (-webkit-min-device-pixel-ratio:0)
|
||||
{
|
||||
.css-treeview{
|
||||
-webkit-animation: webkit-adjacent-element-selector-bugfix infinite 1s;
|
||||
}
|
||||
|
||||
@-webkit-keyframes webkit-adjacent-element-selector-bugfix
|
||||
{
|
||||
from {
|
||||
padding: 0;
|
||||
}
|
||||
to {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#uploader {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
height:28px;
|
||||
line-height: 24px;
|
||||
padding-left: 10px;
|
||||
background-color: #444;
|
||||
color:#EEE;
|
||||
}
|
||||
#tree {
|
||||
position: absolute;
|
||||
top: 28px;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width:200px;
|
||||
padding: 8px;
|
||||
}
|
||||
#editor, #preview {
|
||||
position: absolute;
|
||||
top: 28px;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 200px;
|
||||
}
|
||||
#preview {
|
||||
background-color: #EEE;
|
||||
padding:5px;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
function createFileUploader(element, tree, editor){
|
||||
var xmlHttp;
|
||||
var input = document.createElement("input");
|
||||
input.type = "file";
|
||||
input.multiple = false;
|
||||
input.name = "data";
|
||||
document.getElementById(element).appendChild(input);
|
||||
var path = document.createElement("input");
|
||||
path.id = "upload-path";
|
||||
path.type = "text";
|
||||
path.name = "path";
|
||||
path.defaultValue = "/";
|
||||
document.getElementById(element).appendChild(path);
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = 'Upload';
|
||||
document.getElementById(element).appendChild(button);
|
||||
var mkdir = document.createElement("button");
|
||||
mkdir.innerHTML = 'MkDir';
|
||||
document.getElementById(element).appendChild(mkdir);
|
||||
var mkfile = document.createElement("button");
|
||||
mkfile.innerHTML = 'MkFile';
|
||||
document.getElementById(element).appendChild(mkfile);
|
||||
|
||||
function httpPostProcessRequest(){
|
||||
if (xmlHttp.readyState == 4){
|
||||
if(xmlHttp.status != 200) alert("ERROR["+xmlHttp.status+"]: "+xmlHttp.responseText);
|
||||
else {
|
||||
tree.refreshPath(path.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
function createPath(p){
|
||||
xmlHttp = new XMLHttpRequest();
|
||||
xmlHttp.onreadystatechange = httpPostProcessRequest;
|
||||
var formData = new FormData();
|
||||
formData.append("path", p);
|
||||
xmlHttp.open("PUT", "/edit");
|
||||
xmlHttp.send(formData);
|
||||
}
|
||||
|
||||
mkfile.onclick = function(e){
|
||||
if(path.value.indexOf(".") === -1) return;
|
||||
createPath(path.value);
|
||||
editor.loadUrl(path.value);
|
||||
};
|
||||
mkdir.onclick = function(e){
|
||||
if(path.value.length < 2) return;
|
||||
var dir = path.value
|
||||
if(dir.indexOf(".") !== -1){
|
||||
if(dir.lastIndexOf("/") === 0) return;
|
||||
dir = dir.substring(0, dir.lastIndexOf("/"));
|
||||
}
|
||||
createPath(dir);
|
||||
};
|
||||
button.onclick = function(e){
|
||||
if(input.files.length === 0){
|
||||
return;
|
||||
}
|
||||
xmlHttp = new XMLHttpRequest();
|
||||
xmlHttp.onreadystatechange = httpPostProcessRequest;
|
||||
var formData = new FormData();
|
||||
formData.append("data", input.files[0], path.value);
|
||||
xmlHttp.open("POST", "/edit");
|
||||
xmlHttp.send(formData);
|
||||
}
|
||||
input.onchange = function(e){
|
||||
if(input.files.length === 0) return;
|
||||
var filename = input.files[0].name;
|
||||
var ext = /(?:\.([^.]+))?$/.exec(filename)[1];
|
||||
var name = /(.*)\.[^.]+$/.exec(filename)[1];
|
||||
if(typeof name !== undefined){
|
||||
if(name.length > 8) name = name.substring(0, 8);
|
||||
filename = name;
|
||||
}
|
||||
if(typeof ext !== undefined){
|
||||
if(ext === "html") ext = "htm";
|
||||
else if(ext === "jpeg") ext = "jpg";
|
||||
filename = filename + "." + ext;
|
||||
}
|
||||
if(path.value === "/" || path.value.lastIndexOf("/") === 0){
|
||||
path.value = "/"+filename;
|
||||
} else {
|
||||
path.value = path.value.substring(0, path.value.lastIndexOf("/")+1)+filename;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createTree(element, editor){
|
||||
var preview = document.getElementById("preview");
|
||||
var treeRoot = document.createElement("div");
|
||||
treeRoot.className = "css-treeview";
|
||||
document.getElementById(element).appendChild(treeRoot);
|
||||
|
||||
function loadDownload(path){
|
||||
document.getElementById('download-frame').src = path+"?download=true";
|
||||
}
|
||||
|
||||
function loadPreview(path){
|
||||
document.getElementById("editor").style.display = "none";
|
||||
preview.style.display = "block";
|
||||
preview.innerHTML = '<img src="'+path+'" style="max-width:100%; max-height:100%; margin:auto; display:block;" />';
|
||||
}
|
||||
|
||||
function fillFolderMenu(el, path){
|
||||
var list = document.createElement("ul");
|
||||
el.appendChild(list);
|
||||
var action = document.createElement("li");
|
||||
list.appendChild(action);
|
||||
var isChecked = document.getElementById(path).checked;
|
||||
var expnd = document.createElement("li");
|
||||
list.appendChild(expnd);
|
||||
if(isChecked){
|
||||
expnd.innerHTML = "<span>Collapse</span>";
|
||||
expnd.onclick = function(e){
|
||||
document.getElementById(path).checked = false;
|
||||
if(document.body.getElementsByClassName('contextMenu').length > 0) document.body.removeChild(el);
|
||||
};
|
||||
var refrsh = document.createElement("li");
|
||||
list.appendChild(refrsh);
|
||||
refrsh.innerHTML = "<span>Refresh</span>";
|
||||
refrsh.onclick = function(e){
|
||||
var leaf = document.getElementById(path).parentNode;
|
||||
if(leaf.childNodes.length == 3) leaf.removeChild(leaf.childNodes[2]);
|
||||
httpGet(leaf, path);
|
||||
if(document.body.getElementsByClassName('contextMenu').length > 0) document.body.removeChild(el);
|
||||
};
|
||||
} else {
|
||||
expnd.innerHTML = "<span>Expand</span>";
|
||||
expnd.onclick = function(e){
|
||||
document.getElementById(path).checked = true;
|
||||
var leaf = document.getElementById(path).parentNode;
|
||||
if(leaf.childNodes.length == 3) leaf.removeChild(leaf.childNodes[2]);
|
||||
httpGet(leaf, path);
|
||||
if(document.body.getElementsByClassName('contextMenu').length > 0) document.body.removeChild(el);
|
||||
};
|
||||
}
|
||||
var upload = document.createElement("li");
|
||||
list.appendChild(upload);
|
||||
upload.innerHTML = "<span>Upload</span>";
|
||||
upload.onclick = function(e){
|
||||
var pathEl = document.getElementById("upload-path");
|
||||
if(pathEl){
|
||||
var subPath = pathEl.value;
|
||||
if(subPath.lastIndexOf("/") < 1) pathEl.value = path+subPath;
|
||||
else pathEl.value = path.substring(subPath.lastIndexOf("/"))+subPath;
|
||||
}
|
||||
if(document.body.getElementsByClassName('contextMenu').length > 0) document.body.removeChild(el);
|
||||
};
|
||||
var delFile = document.createElement("li");
|
||||
list.appendChild(delFile);
|
||||
delFile.innerHTML = "<span>Delete</span>";
|
||||
delFile.onclick = function(e){
|
||||
httpDelete(path);
|
||||
if(document.body.getElementsByClassName('contextMenu').length > 0) document.body.removeChild(el);
|
||||
};
|
||||
}
|
||||
|
||||
function fillFileMenu(el, path){
|
||||
var list = document.createElement("ul");
|
||||
el.appendChild(list);
|
||||
var action = document.createElement("li");
|
||||
list.appendChild(action);
|
||||
if(isTextFile(path)){
|
||||
action.innerHTML = "<span>Edit</span>";
|
||||
action.onclick = function(e){
|
||||
editor.loadUrl(path);
|
||||
if(document.body.getElementsByClassName('contextMenu').length > 0) document.body.removeChild(el);
|
||||
};
|
||||
} else if(isImageFile(path)){
|
||||
action.innerHTML = "<span>Preview</span>";
|
||||
action.onclick = function(e){
|
||||
loadPreview(path);
|
||||
if(document.body.getElementsByClassName('contextMenu').length > 0) document.body.removeChild(el);
|
||||
};
|
||||
}
|
||||
var download = document.createElement("li");
|
||||
list.appendChild(download);
|
||||
download.innerHTML = "<span>Download</span>";
|
||||
download.onclick = function(e){
|
||||
loadDownload(path);
|
||||
if(document.body.getElementsByClassName('contextMenu').length > 0) document.body.removeChild(el);
|
||||
};
|
||||
var delFile = document.createElement("li");
|
||||
list.appendChild(delFile);
|
||||
delFile.innerHTML = "<span>Delete</span>";
|
||||
delFile.onclick = function(e){
|
||||
httpDelete(path);
|
||||
if(document.body.getElementsByClassName('contextMenu').length > 0) document.body.removeChild(el);
|
||||
};
|
||||
}
|
||||
|
||||
function showContextMenu(e, path, isfile){
|
||||
var divContext = document.createElement("div");
|
||||
var scrollTop = document.body.scrollTop ? document.body.scrollTop : document.documentElement.scrollTop;
|
||||
var scrollLeft = document.body.scrollLeft ? document.body.scrollLeft : document.documentElement.scrollLeft;
|
||||
var left = e.clientX + scrollLeft;
|
||||
var top = e.clientY + scrollTop;
|
||||
divContext.className = 'contextMenu';
|
||||
divContext.style.display = 'block';
|
||||
divContext.style.left = left + 'px';
|
||||
divContext.style.top = top + 'px';
|
||||
if(isfile) fillFileMenu(divContext, path);
|
||||
else fillFolderMenu(divContext, path);
|
||||
document.body.appendChild(divContext);
|
||||
var width = divContext.offsetWidth;
|
||||
var height = divContext.offsetHeight;
|
||||
divContext.onmouseout = function(e){
|
||||
if(e.clientX < left || e.clientX > (left + width) || e.clientY < top || e.clientY > (top + height)){
|
||||
if(document.body.getElementsByClassName('contextMenu').length > 0) document.body.removeChild(divContext);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function createTreeLeaf(path, name, size){
|
||||
var leaf = document.createElement("li");
|
||||
leaf.id = name.toLowerCase();
|
||||
var label = document.createElement("span");
|
||||
label.textContent = name.toLowerCase();
|
||||
leaf.appendChild(label);
|
||||
leaf.onclick = function(e){
|
||||
if(isTextFile(leaf.id)){
|
||||
editor.loadUrl(leaf.id);
|
||||
} else if(isImageFile(leaf.id)){
|
||||
loadPreview(leaf.id);
|
||||
}
|
||||
};
|
||||
leaf.oncontextmenu = function(e){
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
showContextMenu(e, leaf.id, true);
|
||||
};
|
||||
return leaf;
|
||||
}
|
||||
|
||||
function createTreeBranch(path, name, disabled){
|
||||
var leaf = document.createElement("li");
|
||||
var check = document.createElement("input");
|
||||
check.type = "checkbox";
|
||||
check.id = name.toLowerCase();
|
||||
if(typeof disabled !== "undefined" && disabled) check.disabled = "disabled";
|
||||
leaf.appendChild(check);
|
||||
var label = document.createElement("label");
|
||||
label.for = check.id;
|
||||
label.textContent = name.toLowerCase();
|
||||
leaf.appendChild(label);
|
||||
check.onchange = function(e){
|
||||
if(check.checked){
|
||||
if(leaf.childNodes.length == 3) leaf.removeChild(leaf.childNodes[2]);
|
||||
httpGet(leaf, check.id);
|
||||
}
|
||||
};
|
||||
label.onclick = function(e){
|
||||
if(!check.checked){
|
||||
check.checked = true;
|
||||
if(leaf.childNodes.length == 3) leaf.removeChild(leaf.childNodes[2]);
|
||||
httpGet(leaf, check.id);
|
||||
} else {
|
||||
check.checked = false;
|
||||
}
|
||||
};
|
||||
leaf.oncontextmenu = function(e){
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
showContextMenu(e, check.id, false);
|
||||
}
|
||||
return leaf;
|
||||
}
|
||||
|
||||
function addList(parent, path, items){
|
||||
var list = document.createElement("ul");
|
||||
parent.appendChild(list);
|
||||
var ll = items.length;
|
||||
for(var i = 0; i < ll; i++){
|
||||
var item = items[i];
|
||||
var itemEl;
|
||||
if(item.type === "file"){
|
||||
itemEl = createTreeLeaf(path, item.name, item.size);
|
||||
} else {
|
||||
itemEl = createTreeBranch(path, item.name);
|
||||
}
|
||||
list.appendChild(itemEl);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function isTextFile(path){
|
||||
var ext = /(?:\.([^.]+))?$/.exec(path)[1];
|
||||
if(typeof ext !== undefined){
|
||||
switch(ext){
|
||||
case "txt":
|
||||
case "htm":
|
||||
case "html":
|
||||
case "js":
|
||||
case "json":
|
||||
case "c":
|
||||
case "h":
|
||||
case "cpp":
|
||||
case "css":
|
||||
case "xml":
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function isImageFile(path){
|
||||
var ext = /(?:\.([^.]+))?$/.exec(path)[1];
|
||||
if(typeof ext !== undefined){
|
||||
switch(ext){
|
||||
case "png":
|
||||
case "jpg":
|
||||
case "gif":
|
||||
case "ico":
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
this.refreshPath = function(path){
|
||||
if(path.lastIndexOf('/') < 1){
|
||||
path = '/';
|
||||
treeRoot.removeChild(treeRoot.childNodes[0]);
|
||||
httpGet(treeRoot, "/");
|
||||
} else {
|
||||
path = path.substring(0, path.lastIndexOf('/'));
|
||||
var leaf = document.getElementById(path).parentNode;
|
||||
if(leaf.childNodes.length == 3) leaf.removeChild(leaf.childNodes[2]);
|
||||
httpGet(leaf, path);
|
||||
}
|
||||
};
|
||||
|
||||
function delCb(path){
|
||||
return function(){
|
||||
if (xmlHttp.readyState == 4){
|
||||
if(xmlHttp.status != 200){
|
||||
alert("ERROR["+xmlHttp.status+"]: "+xmlHttp.responseText);
|
||||
} else {
|
||||
if(path.lastIndexOf('/') < 1){
|
||||
path = '/';
|
||||
treeRoot.removeChild(treeRoot.childNodes[0]);
|
||||
httpGet(treeRoot, "/");
|
||||
} else {
|
||||
path = path.substring(0, path.lastIndexOf('/'));
|
||||
var leaf = document.getElementById(path).parentNode;
|
||||
if(leaf.childNodes.length == 3) leaf.removeChild(leaf.childNodes[2]);
|
||||
httpGet(leaf, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function httpDelete(filename){
|
||||
xmlHttp = new XMLHttpRequest();
|
||||
xmlHttp.onreadystatechange = delCb(filename);
|
||||
var formData = new FormData();
|
||||
formData.append("path", filename);
|
||||
xmlHttp.open("DELETE", "/edit");
|
||||
xmlHttp.send(formData);
|
||||
}
|
||||
|
||||
function getCb(parent, path){
|
||||
return function(){
|
||||
if (xmlHttp.readyState == 4){
|
||||
//clear loading
|
||||
if(xmlHttp.status == 200) addList(parent, path, JSON.parse(xmlHttp.responseText));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function httpGet(parent, path){
|
||||
xmlHttp = new XMLHttpRequest(parent, path);
|
||||
xmlHttp.onreadystatechange = getCb(parent, path);
|
||||
xmlHttp.open("GET", "/list?dir="+path, true);
|
||||
xmlHttp.send(null);
|
||||
//start loading
|
||||
}
|
||||
|
||||
httpGet(treeRoot, "/");
|
||||
return this;
|
||||
}
|
||||
|
||||
function createEditor(element, file, lang, theme, type){
|
||||
function getLangFromFilename(filename){
|
||||
var lang = "plain";
|
||||
var ext = /(?:\.([^.]+))?$/.exec(filename)[1];
|
||||
if(typeof ext !== undefined){
|
||||
switch(ext){
|
||||
case "txt": lang = "plain"; break;
|
||||
case "htm": lang = "html"; break;
|
||||
case "js": lang = "javascript"; break;
|
||||
case "c": lang = "c_cpp"; break;
|
||||
case "cpp": lang = "c_cpp"; break;
|
||||
case "css":
|
||||
case "scss":
|
||||
case "php":
|
||||
case "html":
|
||||
case "json":
|
||||
case "xml":
|
||||
lang = ext;
|
||||
}
|
||||
}
|
||||
return lang;
|
||||
}
|
||||
|
||||
if(typeof file === "undefined") file = "/index.htm";
|
||||
|
||||
if(typeof lang === "undefined"){
|
||||
lang = getLangFromFilename(file);
|
||||
}
|
||||
|
||||
if(typeof theme === "undefined") theme = "textmate";
|
||||
|
||||
if(typeof type === "undefined"){
|
||||
type = "text/"+lang;
|
||||
if(lang === "c_cpp") type = "text/plain";
|
||||
}
|
||||
|
||||
var xmlHttp = null;
|
||||
var editor = ace.edit(element);
|
||||
|
||||
//post
|
||||
function httpPostProcessRequest(){
|
||||
if (xmlHttp.readyState == 4){
|
||||
if(xmlHttp.status != 200) alert("ERROR["+xmlHttp.status+"]: "+xmlHttp.responseText);
|
||||
}
|
||||
}
|
||||
function httpPost(filename, data, type){
|
||||
xmlHttp = new XMLHttpRequest();
|
||||
xmlHttp.onreadystatechange = httpPostProcessRequest;
|
||||
var formData = new FormData();
|
||||
formData.append("data", new Blob([data], { type: type }), filename);
|
||||
xmlHttp.open("POST", "/edit");
|
||||
xmlHttp.send(formData);
|
||||
}
|
||||
//get
|
||||
function httpGetProcessRequest(){
|
||||
if (xmlHttp.readyState == 4){
|
||||
document.getElementById("preview").style.display = "none";
|
||||
document.getElementById("editor").style.display = "block";
|
||||
if(xmlHttp.status == 200) editor.setValue(xmlHttp.responseText);
|
||||
else editor.setValue("");
|
||||
editor.clearSelection();
|
||||
}
|
||||
}
|
||||
function httpGet(theUrl){
|
||||
xmlHttp = new XMLHttpRequest();
|
||||
xmlHttp.onreadystatechange = httpGetProcessRequest;
|
||||
xmlHttp.open("GET", theUrl, true);
|
||||
xmlHttp.send(null);
|
||||
}
|
||||
|
||||
if(lang !== "plain") editor.getSession().setMode("ace/mode/"+lang);
|
||||
editor.setTheme("ace/theme/"+theme);
|
||||
editor.$blockScrolling = Infinity;
|
||||
editor.getSession().setUseSoftTabs(true);
|
||||
editor.getSession().setTabSize(2);
|
||||
editor.setHighlightActiveLine(true);
|
||||
editor.setShowPrintMargin(false);
|
||||
editor.commands.addCommand({
|
||||
name: 'saveCommand',
|
||||
bindKey: {win: 'Ctrl-S', mac: 'Command-S'},
|
||||
exec: function(editor) {
|
||||
httpPost(file, editor.getValue()+"", type);
|
||||
},
|
||||
readOnly: false
|
||||
});
|
||||
editor.commands.addCommand({
|
||||
name: 'undoCommand',
|
||||
bindKey: {win: 'Ctrl-Z', mac: 'Command-Z'},
|
||||
exec: function(editor) {
|
||||
editor.getSession().getUndoManager().undo(false);
|
||||
},
|
||||
readOnly: false
|
||||
});
|
||||
editor.commands.addCommand({
|
||||
name: 'redoCommand',
|
||||
bindKey: {win: 'Ctrl-Shift-Z', mac: 'Command-Shift-Z'},
|
||||
exec: function(editor) {
|
||||
editor.getSession().getUndoManager().redo(false);
|
||||
},
|
||||
readOnly: false
|
||||
});
|
||||
httpGet(file);
|
||||
editor.loadUrl = function(filename){
|
||||
file = filename;
|
||||
lang = getLangFromFilename(file);
|
||||
type = "text/"+lang;
|
||||
if(lang !== "plain") editor.getSession().setMode("ace/mode/"+lang);
|
||||
httpGet(file);
|
||||
}
|
||||
return editor;
|
||||
}
|
||||
function onBodyLoad(){
|
||||
var vars = {};
|
||||
var parts = window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, function(m,key,value) { vars[key] = value; });
|
||||
var editor = createEditor("editor", vars.file, vars.lang, vars.theme);
|
||||
var tree = createTree("tree", editor);
|
||||
createFileUploader("uploader", tree, editor);
|
||||
};
|
||||
</script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.1.9/ace.js" type="text/javascript" charset="utf-8"></script>
|
||||
</head>
|
||||
<body onload="onBodyLoad();">
|
||||
<div id="uploader"></div>
|
||||
<div id="tree"></div>
|
||||
<div id="editor"></div>
|
||||
<div id="preview" style="display:none;"></div>
|
||||
<iframe id=download-frame style='display:none;'></iframe>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
|
||||
<title>ESP Index</title>
|
||||
<style>
|
||||
body {
|
||||
background-color:black;
|
||||
color:white;
|
||||
}
|
||||
</style>
|
||||
<script type="text/javascript">
|
||||
function onBodyLoad(){
|
||||
console.log("we are loaded!!");
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body id="index" onload="onBodyLoad()">
|
||||
<h1>ESP8266 Pin Functions</h1>
|
||||
<img src="pins.png" />
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 174 KiB |
@@ -0,0 +1,3 @@
|
||||
requires_any:
|
||||
- CONFIG_SOC_WIFI_SUPPORTED=y
|
||||
- CONFIG_ESP_WIFI_REMOTE_ENABLED=y
|
||||
@@ -0,0 +1,132 @@
|
||||
#include <WiFi.h>
|
||||
#include <NetworkClient.h>
|
||||
#include <WebServer.h>
|
||||
|
||||
const char *ssid = "........";
|
||||
const char *password = "........";
|
||||
|
||||
WebServer server(80);
|
||||
|
||||
//Check if header is present and correct
|
||||
bool is_authentified() {
|
||||
Serial.println("Enter is_authentified");
|
||||
if (server.hasHeader("Cookie")) {
|
||||
Serial.print("Found cookie: ");
|
||||
String cookie = server.header("Cookie");
|
||||
Serial.println(cookie);
|
||||
if (cookie.indexOf("ESPSESSIONID=1") != -1) {
|
||||
Serial.println("Authentication Successful");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Serial.println("Authentication Failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
//login page, also called for disconnect
|
||||
void handleLogin() {
|
||||
String msg;
|
||||
if (server.hasHeader("Cookie")) {
|
||||
Serial.print("Found cookie: ");
|
||||
String cookie = server.header("Cookie");
|
||||
Serial.println(cookie);
|
||||
}
|
||||
if (server.hasArg("DISCONNECT")) {
|
||||
Serial.println("Disconnection");
|
||||
server.sendHeader("Location", "/login");
|
||||
server.sendHeader("Cache-Control", "no-cache");
|
||||
server.sendHeader("Set-Cookie", "ESPSESSIONID=0");
|
||||
server.send(301);
|
||||
return;
|
||||
}
|
||||
if (server.hasArg("USERNAME") && server.hasArg("PASSWORD")) {
|
||||
if (server.arg("USERNAME") == "admin" && server.arg("PASSWORD") == "admin") {
|
||||
server.sendHeader("Location", "/");
|
||||
server.sendHeader("Cache-Control", "no-cache");
|
||||
server.sendHeader("Set-Cookie", "ESPSESSIONID=1");
|
||||
server.send(301);
|
||||
Serial.println("Log in Successful");
|
||||
return;
|
||||
}
|
||||
msg = "Wrong username/password! try again.";
|
||||
Serial.println("Log in Failed");
|
||||
}
|
||||
String content = "<html><body><form action='/login' method='POST'>To log in, please use : admin/admin<br>";
|
||||
content += "User:<input type='text' name='USERNAME' placeholder='user name'><br>";
|
||||
content += "Password:<input type='password' name='PASSWORD' placeholder='password'><br>";
|
||||
content += "<input type='submit' name='SUBMIT' value='Submit'></form>" + msg + "<br>";
|
||||
content += "You also can go <a href='/inline'>here</a></body></html>";
|
||||
server.send(200, "text/html", content);
|
||||
}
|
||||
|
||||
//root page can be accessed only if authentication is ok
|
||||
void handleRoot() {
|
||||
Serial.println("Enter handleRoot");
|
||||
String header;
|
||||
if (!is_authentified()) {
|
||||
server.sendHeader("Location", "/login");
|
||||
server.sendHeader("Cache-Control", "no-cache");
|
||||
server.send(301);
|
||||
return;
|
||||
}
|
||||
String content = "<html><body><H2>hello, you successfully connected to esp8266!</H2><br>";
|
||||
if (server.hasHeader("User-Agent")) {
|
||||
content += "the user agent used is : " + server.header("User-Agent") + "<br><br>";
|
||||
}
|
||||
content += "You can access this page until you <a href=\"/login?DISCONNECT=YES\">disconnect</a></body></html>";
|
||||
server.send(200, "text/html", content);
|
||||
}
|
||||
|
||||
//no need authentication
|
||||
void handleNotFound() {
|
||||
String message = "File Not Found\n\n";
|
||||
message += "URI: ";
|
||||
message += server.uri();
|
||||
message += "\nMethod: ";
|
||||
message += (server.method() == HTTP_GET) ? "GET" : "POST";
|
||||
message += "\nArguments: ";
|
||||
message += server.args();
|
||||
message += "\n";
|
||||
for (uint8_t i = 0; i < server.args(); i++) {
|
||||
message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
|
||||
}
|
||||
server.send(404, "text/plain", message);
|
||||
}
|
||||
|
||||
void setup(void) {
|
||||
Serial.begin(115200);
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.begin(ssid, password);
|
||||
Serial.println("");
|
||||
|
||||
// Wait for connection
|
||||
while (WiFi.status() != WL_CONNECTED) {
|
||||
delay(500);
|
||||
Serial.print(".");
|
||||
}
|
||||
Serial.println("");
|
||||
Serial.print("Connected to ");
|
||||
Serial.println(ssid);
|
||||
Serial.print("IP address: ");
|
||||
Serial.println(WiFi.localIP());
|
||||
|
||||
server.on("/", handleRoot);
|
||||
server.on("/login", handleLogin);
|
||||
server.on("/inline", []() {
|
||||
server.send(200, "text/plain", "this works without need of authentication");
|
||||
});
|
||||
|
||||
server.onNotFound(handleNotFound);
|
||||
//here the list of headers to be recorded
|
||||
const char *headerkeys[] = {"User-Agent", "Cookie"};
|
||||
size_t headerkeyssize = sizeof(headerkeys) / sizeof(char *);
|
||||
//ask server to track these headers
|
||||
server.collectHeaders(headerkeys, headerkeyssize);
|
||||
server.begin();
|
||||
Serial.println("HTTP server started");
|
||||
}
|
||||
|
||||
void loop(void) {
|
||||
server.handleClient();
|
||||
delay(2); //allow the cpu to switch to other tasks
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
requires_any:
|
||||
- CONFIG_SOC_WIFI_SUPPORTED=y
|
||||
- CONFIG_ESP_WIFI_REMOTE_ENABLED=y
|
||||
@@ -0,0 +1,13 @@
|
||||
# Upload Huge File To SD Over Http
|
||||
|
||||
This project is an example of an HTTP server designed to facilitate the transfer of large files using the PUT method, in accordance with RFC specifications.
|
||||
|
||||
### Example cURL Command
|
||||
|
||||
```bash
|
||||
curl -X PUT -T ./my-file.mp3 http://esp-ip/upload/my-file.mp3
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- RFC HTTP/1.0 - Additional Request Methods - PUT : [Link](https://datatracker.ietf.org/doc/html/rfc1945#appendix-D.1.1)
|
||||
@@ -0,0 +1,88 @@
|
||||
#include <WiFi.h>
|
||||
#include <WebServer.h>
|
||||
#include <uri/UriRegex.h>
|
||||
#include <SD.h>
|
||||
|
||||
const char *ssid = "**********";
|
||||
const char *password = "**********";
|
||||
|
||||
WebServer server(80);
|
||||
|
||||
File rawFile;
|
||||
void handleCreate() {
|
||||
server.send(200, "text/plain", "");
|
||||
}
|
||||
void handleCreateProcess() {
|
||||
String path = "/" + server.pathArg(0);
|
||||
HTTPRaw &raw = server.raw();
|
||||
if (raw.status == RAW_START) {
|
||||
if (SD.exists((char *)path.c_str())) {
|
||||
SD.remove((char *)path.c_str());
|
||||
}
|
||||
rawFile = SD.open(path.c_str(), FILE_WRITE);
|
||||
Serial.print("Upload: START, filename: ");
|
||||
Serial.println(path);
|
||||
} else if (raw.status == RAW_WRITE) {
|
||||
if (rawFile) {
|
||||
rawFile.write(raw.buf, raw.currentSize);
|
||||
}
|
||||
Serial.print("Upload: WRITE, Bytes: ");
|
||||
Serial.println(raw.currentSize);
|
||||
} else if (raw.status == RAW_END) {
|
||||
if (rawFile) {
|
||||
rawFile.close();
|
||||
}
|
||||
Serial.print("Upload: END, Size: ");
|
||||
Serial.println(raw.totalSize);
|
||||
}
|
||||
}
|
||||
|
||||
void returnFail(String msg) {
|
||||
server.send(500, "text/plain", msg + "\r\n");
|
||||
}
|
||||
|
||||
void handleNotFound() {
|
||||
String message = "File Not Found\n\n";
|
||||
message += "URI: ";
|
||||
message += server.uri();
|
||||
message += "\nMethod: ";
|
||||
message += (server.method() == HTTP_GET) ? "GET" : "POST";
|
||||
message += "\nArguments: ";
|
||||
message += server.args();
|
||||
message += "\n";
|
||||
for (uint8_t i = 0; i < server.args(); i++) {
|
||||
message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
|
||||
}
|
||||
server.send(404, "text/plain", message);
|
||||
}
|
||||
|
||||
void setup(void) {
|
||||
Serial.begin(115200);
|
||||
|
||||
while (!SD.begin()) {
|
||||
delay(1);
|
||||
}
|
||||
Serial.println("SD Card initialized.");
|
||||
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.begin(ssid, password);
|
||||
|
||||
while (WiFi.status() != WL_CONNECTED) {
|
||||
delay(500);
|
||||
Serial.print(".");
|
||||
}
|
||||
Serial.print("Connected to ");
|
||||
Serial.println(ssid);
|
||||
Serial.print("IP address: ");
|
||||
Serial.println(WiFi.localIP());
|
||||
|
||||
server.on(UriRegex("/upload/(.*)"), HTTP_PUT, handleCreate, handleCreateProcess);
|
||||
server.onNotFound(handleNotFound);
|
||||
server.begin();
|
||||
Serial.println("HTTP server started");
|
||||
}
|
||||
|
||||
void loop(void) {
|
||||
server.handleClient();
|
||||
delay(2); //allow the cpu to switch to other tasks
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
fqbn_append: PartitionScheme=huge_app
|
||||
|
||||
requires_any:
|
||||
- CONFIG_SOC_WIFI_SUPPORTED=y
|
||||
- CONFIG_ESP_WIFI_REMOTE_ENABLED=y
|
||||
@@ -0,0 +1,297 @@
|
||||
# Arduino-ESP32 WebServer Example for WebServer Library
|
||||
|
||||
This example shows different techniques on how to use and extend the WebServer for specific purposes
|
||||
|
||||
It is a small project in it's own and has some files to use on the web server to show how to use simple REST based services.
|
||||
|
||||
This example requires some space for a filesystem and runs fine on supported SoCs with 4 MByte or more flash by selecting the proper partition table:
|
||||
|
||||
* For using SPIFFS(LittleFS): `Default 4MB with spiffs (1.2MB APP/1.5MB SPIFFS)`
|
||||
* For using FATFS: `Default 4MB with ffat (1.2MB APP/1.5MB FATFS)`
|
||||
|
||||
|
||||
It features
|
||||
|
||||
* Setup a web server
|
||||
* redirect when accessing the url with servername only
|
||||
* get real time by using builtin NTP functionality
|
||||
* send HTML responses from Sketch (see builtinfiles.h)
|
||||
* use a LittleFS file system on the data partition for static files
|
||||
* use http ETag Header for client side caching of static files
|
||||
* use custom ETag calculation for static files
|
||||
* extended FileServerHandler for uploading and deleting static files
|
||||
* uploading files using drag & drop
|
||||
* serve APIs using REST services (/api/list, /api/sysinfo)
|
||||
* define HTML response when no file/api/handler was found
|
||||
|
||||
## Supported Targets
|
||||
|
||||
Currently, this example supports the following targets.
|
||||
|
||||
| Supported Targets | ESP32 | ESP32-S2 | ESP32-S3 | ESP32-C3 | ESP32-C6 |
|
||||
| ----------------- | ----- | -------- | -------- | -------- | -------- |
|
||||
| | yes | yes | yes | yes | yes |
|
||||
|
||||
## Use the Example
|
||||
|
||||
How to install the Arduino IDE: [Install Arduino IDE](https://github.com/espressif/arduino-esp32/tree/master/docs/arduino-ide).
|
||||
|
||||
* In the file `secrets.h` you can add the home Wi-Fi network name ans password.
|
||||
* Compile and upload to the device.
|
||||
* Have a look into the monitoring output.
|
||||
* Open <http://webserver> or <http://(ip-address)> using a browser.
|
||||
* You will be redirected to <http://webserver/$upload.htm> as there are no files yet in the file system.
|
||||
* Drag the files from the data folder onto the drop area shown in the browser.
|
||||
* See below for more details
|
||||
|
||||
## Implementing a web server
|
||||
|
||||
The WebServer library offers a simple path to implement a web server on a ESP32 based board.
|
||||
|
||||
The advantage on using the WebServer instead of the plain simple NetworkServer is that the WebServer
|
||||
takes much care about the http protocol conventions and features and allows easily access to parameters.
|
||||
It offers plug-in capabilities by registering specific functionalities that will be outlined below.
|
||||
|
||||
### Initialization
|
||||
|
||||
In the setup() function in the webserver.ino sketch file the following steps are implemented to make the webserver available on the local network.
|
||||
|
||||
* Create a webserver listening to port 80 for HTTP requests.
|
||||
* Initialize the access to the filesystem in the free flash memory.
|
||||
* Connect to the local Wi-Fi network. Here is only a straight-forward implementation hard-coding network name and passphrase. You may consider to use something like the WiFiManager library in real applications.
|
||||
* Register the device in DNS using a known hostname.
|
||||
* Registering several plug-ins (see below).
|
||||
* Starting the web server.
|
||||
|
||||
### Running
|
||||
|
||||
In the loop() function the web server will be given time to receive and send network packages by calling
|
||||
`server.handleClient();`.
|
||||
|
||||
## Registering simple functions to implement RESTful services
|
||||
|
||||
Registering function is the simplest integration mechanism available to add functionality. The server offers the `on(path, function)` methods that take the URL and the function as parameters.
|
||||
|
||||
There are 2 functions implemented that get registered to handle incoming GET requests for given URLs.
|
||||
|
||||
The JSON data format is used often for such services as it is the "natural" data format of the browser using javascript.
|
||||
|
||||
When the **handleSysInfo()** function is registered and a browser requests for <http://webserver/api/sysinfo> the function will be called and can collect the requested information.
|
||||
|
||||
> ```CPP
|
||||
> server.on("/api/sysinfo", handleSysInfo);
|
||||
> ```
|
||||
|
||||
The result in this case is a JSON object that is assembled in the result String variable and the returned as a response to the client also giving the information about the data format.
|
||||
|
||||
You can try this request in a browser by opening <http://webserver/api/sysinfo> in the address bar.
|
||||
|
||||
> ```CPP
|
||||
> server.on("/api/sysinfo", handleList);
|
||||
> ```
|
||||
|
||||
The function **handleList()** is registered the same way to return the list of files in the file system also returning a JSON object including name, size and the last modification timestamp.
|
||||
|
||||
You can try this request in a browser by opening <http://webserver/api/list> in the address bar.
|
||||
|
||||
## Registering a function to send out some static content from a String
|
||||
|
||||
This is an example of registering a inline function in the web server.
|
||||
The 2. parameter of the on() method is a so called CPP lambda function (without a name)
|
||||
that actually has only one line of functionality by sending a string as result to the client.
|
||||
|
||||
> ``` cpp
|
||||
> server.on("/$upload.htm", []() {
|
||||
> server.send(200, "text/html", FPSTR(uploadContent));
|
||||
> });
|
||||
> ```
|
||||
|
||||
Here the text from a static String with html code is returned instead of a file from the filesystem.
|
||||
The content of this string can be found in the file `builtinfiles.h`. It contains a small html+javascript implementation
|
||||
that allows uploading new files into the empty filesystem.
|
||||
|
||||
Just open <http://webserver/$upload.htm> and drag some files from the data folder on the drop area.
|
||||
|
||||
## Registering a function to handle requests to the server without a path
|
||||
|
||||
Often servers are addressed by using the base URL like <http://webserver/> where no further path details is given.
|
||||
Of course we like the user to be redirected to something usable. Therefore the `handleRoot()` function is registered:
|
||||
|
||||
> ``` cpp
|
||||
> server.on("/$upload.htm", handleRoot);
|
||||
> ```
|
||||
|
||||
The `handleRoot()` function checks the filesystem for the file named **/index.htm** and creates a redirect to this file when the file exists.
|
||||
Otherwise the redirection goes to the built-in **/$upload.htm** web page.
|
||||
|
||||
## Using the serveStatic plug-in
|
||||
|
||||
The **serveStatic** plug in is part of the library and handles delivering files from the filesystem to the client. It can be customized in some ways.
|
||||
|
||||
> ``` cpp
|
||||
> server.enableCORS(true);
|
||||
> server.enableETag(true);
|
||||
> server.serveStatic("/", LittleFS, "/");
|
||||
> ```
|
||||
|
||||
### Cross-Origin Resource Sharing (CORS)
|
||||
|
||||
The `enableCORS(true)` function adds a `Access-Control-Allow-Origin: *` http-header to all responses to the client
|
||||
to inform that it is allowed to call URLs and services on this server from other web sites.
|
||||
|
||||
The feature is disabled by default (in the current version) and when you like to disable this then you should call `enableCORS(false)` during setup.
|
||||
|
||||
* Web sites providing high sensitive information like online banking this is disabled most of the times.
|
||||
* Web sites providing advertising information or reusable scripts / images this is enabled.
|
||||
|
||||
### enabling ETag support
|
||||
|
||||
To enable this in the embedded web server the `enableETag()` can be used.
|
||||
(next to enableCORS)
|
||||
|
||||
In the simplest version just call `enableETag(true)` to enable the internal ETag generation that calcs the hint using a md5 checksum in base64 encoded form. This is an simple approach that adds some time for calculation on every request but avoids network traffic.
|
||||
|
||||
The headers will look like:
|
||||
|
||||
``` txt
|
||||
If-None-Match: "GhZka3HevoaEBbtQOgOqlA=="
|
||||
ETag: "GhZka3HevoaEBbtQOgOqlA=="
|
||||
```
|
||||
|
||||
|
||||
### ETag support customization
|
||||
|
||||
The enableETag() function has an optional second optional parameter to provide a function for ETag calculation of files.
|
||||
|
||||
The function enables eTags for all files by using calculating a value from the last write timestamp:
|
||||
|
||||
``` cpp
|
||||
server.enableETag(true, [](FS &fs, const String &path) -> String {
|
||||
File f = fs.open(path, "r");
|
||||
String eTag = String(f.getLastWrite(), 16); // use file modification timestamp to create ETag
|
||||
f.close();
|
||||
return (eTag);
|
||||
});
|
||||
```
|
||||
|
||||
The headers will look like:
|
||||
|
||||
``` txt
|
||||
ETag: "63bbaeb5"
|
||||
If-None-Match: "63bbaeb5"
|
||||
```
|
||||
|
||||
|
||||
## Registering a full-featured handler as plug-in
|
||||
|
||||
The example also implements the class `FileServerHandler` derived from the class `RequestHandler` to plug in functionality
|
||||
that can handle more complex requests without giving a fixed URL.
|
||||
It implements uploading and deleting files in the file system that is not implemented by the standard server.serveStatic functionality.
|
||||
|
||||
This class has to implements several functions and works in a more detailed way:
|
||||
|
||||
* The `canHandle()` method can inspect the given http method and url to decide weather the RequestFileHandler can handle the incoming request or not.
|
||||
|
||||
In this case the RequestFileHandler will return true when the request method is an POST for upload or a DELETE for deleting files.
|
||||
|
||||
The regular GET requests will be ignored and therefore handled by the also registered server.serveStatic handler.
|
||||
|
||||
* The function `handle()` then implements the real deletion of the file.
|
||||
|
||||
* The `canUpload()`and `upload()` methods work similar while the `upload()` method is called multiple times to create, append data and close the new file.
|
||||
|
||||
|
||||
## File upload
|
||||
|
||||
By opening <http://webserver/$upload.htm> you can easily upload files by dragging them over the drop area.
|
||||
|
||||
Just take the files from the data folder to create some files that can explore the server functionality.
|
||||
|
||||
Files will be uploaded to the root folder of the file system. and you will see it next time using <http://webserver/files.htm>.
|
||||
|
||||
The filesize that is uploaded is not known when the upload mechanism in function
|
||||
FileServerHandler::upload gets started.
|
||||
|
||||
Uploading a file that fits into the available filesystem space
|
||||
can be found in the Serial output:
|
||||
|
||||
``` txt
|
||||
starting upload file /file.txt...
|
||||
finished.
|
||||
1652 bytes uploaded.
|
||||
```
|
||||
|
||||
Uploading a file that doesn't fit can be detected while uploading when writing to the filesystem fails.
|
||||
However upload cannot be aborted by the current handler implementation.
|
||||
|
||||
The solution implemented here is to delete the partially uploaded file and wait for the upload ending.
|
||||
The following can be found in the Serial output:
|
||||
|
||||
``` txt
|
||||
starting upload file /huge.jpg...
|
||||
./components/esp_littlefs/src/littlefs/lfs.c:584:error: No more free space 531
|
||||
write error!
|
||||
finished.
|
||||
```
|
||||
|
||||
You can see on the Serial output that one filesystem write error is reported.
|
||||
|
||||
Please be patient and wait for the upload ending even when writing to the filesystem is disabled
|
||||
it maybe take more than a minute.
|
||||
|
||||
|
||||
## Registering a special handler for "file not found"
|
||||
|
||||
Any other incoming request that was not handled by the registered plug-ins above can be detected by registering
|
||||
|
||||
> ``` cpp
|
||||
> // handle cases when file is not found
|
||||
> server.onNotFound([]() {
|
||||
> // standard not found in browser.
|
||||
> server.send(404, "text/html", FPSTR(notFoundContent));
|
||||
> });
|
||||
> ```
|
||||
|
||||
This allows sending back an "friendly" result for the browser. Here a simple html page is created from a static string.
|
||||
You can easily change the html code in the file `builtinfiles.h`.
|
||||
|
||||
|
||||
## customizations
|
||||
|
||||
You may like to change the hostname and the timezone in the lines:
|
||||
|
||||
> ``` cpp
|
||||
> #define HOSTNAME "webserver"
|
||||
> #define TIMEZONE "CET-1CEST,M3.5.0,M10.5.0/3"
|
||||
> ```
|
||||
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
Have a look in the Serial output for some additional runtime information.
|
||||
|
||||
|
||||
## Changes
|
||||
|
||||
* 2024-08-02 -- Fixing for board implementation 3.0.4 ff.
|
||||
* 2024-08-02 -- Support for FAT
|
||||
|
||||
|
||||
## Contribute
|
||||
|
||||
To know how to contribute to this project, see [How to contribute.](https://github.com/espressif/arduino-esp32/blob/master/CONTRIBUTING.rst)
|
||||
|
||||
If you have any **feedback** or **issue** to report on this example/library, please open an issue or fix it by creating a new PR. Contributions are more than welcome!
|
||||
|
||||
Before creating a new issue, be sure to try Troubleshooting and check if the same issue was already created by someone else.
|
||||
|
||||
|
||||
## Resources
|
||||
|
||||
* Official ESP32 Forum: [Link](https://esp32.com)
|
||||
* Arduino-ESP32 Official Repository: [espressif/arduino-esp32](https://github.com/espressif/arduino-esp32)
|
||||
* ESP32 Datasheet: [Link to datasheet](https://www.espressif.com/sites/default/files/documentation/esp32_datasheet_en.pdf)
|
||||
* ESP32-S2 Datasheet: [Link to datasheet](https://www.espressif.com/sites/default/files/documentation/esp32-s2_datasheet_en.pdf)
|
||||
* ESP32-S3 Datasheet: [Link to datasheet](https://www.espressif.com/sites/default/files/documentation/esp32-s3_datasheet_en.pdf)
|
||||
* ESP32-C3 Datasheet: [Link to datasheet](https://www.espressif.com/sites/default/files/documentation/esp32-c3_datasheet_en.pdf)
|
||||
* Official ESP-IDF documentation: [ESP-IDF](https://idf.espressif.com)
|
||||
@@ -0,0 +1,376 @@
|
||||
// @file WebServer.ino
|
||||
// @brief Example WebServer implementation using the ESP32 WebServer
|
||||
// and most common use cases related to web servers.
|
||||
//
|
||||
// * Setup a web server
|
||||
// * redirect when accessing the url with servername only
|
||||
// * get real time by using builtin NTP functionality
|
||||
// * send HTML responses from Sketch (see builtinfiles.h)
|
||||
// * use a LittleFS file system on the data partition for static files
|
||||
// * use http ETag Header for client side caching of static files
|
||||
// * use custom ETag calculation for static files
|
||||
// * extended FileServerHandler for uploading and deleting static files
|
||||
// * extended FileServerHandler for uploading and deleting static files
|
||||
// * serve APIs using REST services (/api/list, /api/sysinfo)
|
||||
// * define HTML response when no file/api/handler was found
|
||||
//
|
||||
// See also README.md for instructions and hints.
|
||||
//
|
||||
// Please use the following Arduino IDE configuration
|
||||
//
|
||||
// * Board: "ESP32 Dev Module" or other board with ESP32
|
||||
// * Partition Scheme: "Default 4MB with spiffs" or any other scheme with spiffs or FAT
|
||||
// but LittleFS will be used in the partition (not SPIFFS)
|
||||
// * other setting as applicable
|
||||
//
|
||||
// Changelog:
|
||||
// 21.07.2021 creation, first version
|
||||
// 08.01.2023 ESP32 version with ETag
|
||||
// 02.08.2024 support LitteFS and FAT file systems (on large Flash chips)
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <WebServer.h>
|
||||
#include <WiFi.h>
|
||||
|
||||
#include "secrets.h" // add WLAN Credentials in here.
|
||||
|
||||
#include "esp_partition.h" // to check existing data partitions in Flash memory
|
||||
|
||||
#include <FS.h> // File System for Web Server Files
|
||||
#include <LittleFS.h> // Use LittleFSThis file system is used.
|
||||
#include <FFat.h> // or.. FAT
|
||||
|
||||
// mark parameters not used in example
|
||||
#define UNUSED __attribute__((unused))
|
||||
|
||||
// TRACE output simplified, can be deactivated here
|
||||
#define TRACE(...) Serial.printf(__VA_ARGS__)
|
||||
|
||||
// name of the server. You reach it using http://webserver
|
||||
#define HOSTNAME "webserver"
|
||||
|
||||
// local time zone definition (Berlin)
|
||||
#define TIMEZONE "CET-1CEST,M3.5.0,M10.5.0/3"
|
||||
|
||||
// need a WebServer for http access on port 80.
|
||||
WebServer server(80);
|
||||
|
||||
// The file system in use...
|
||||
fs::FS *fsys = nullptr;
|
||||
|
||||
// The text of builtin files are in this header file
|
||||
#include "builtinfiles.h"
|
||||
|
||||
// enable the CUSTOM_ETAG_CALC to enable calculation of ETags by a custom function
|
||||
#define CUSTOM_ETAG_CALC
|
||||
|
||||
// ===== Simple functions used to answer simple GET requests =====
|
||||
|
||||
// This function is called when the WebServer was requested without giving a filename.
|
||||
// This will redirect to the file index.htm when it is existing otherwise to the built-in $upload.htm page
|
||||
void handleRedirect() {
|
||||
TRACE("Redirect...\n");
|
||||
String url = "/index.htm";
|
||||
|
||||
if (!fsys->exists(url)) {
|
||||
url = "/$upload.htm";
|
||||
}
|
||||
|
||||
server.sendHeader("Location", url, true);
|
||||
server.send(302);
|
||||
} // handleRedirect()
|
||||
|
||||
// This function is called when the WebServer was requested to list all existing files in the filesystem.
|
||||
// a JSON array with file information is returned.
|
||||
void handleListFiles() {
|
||||
File dir = fsys->open("/", "r");
|
||||
String result;
|
||||
|
||||
result += "[\n";
|
||||
while (File entry = dir.openNextFile()) {
|
||||
if (result.length() > 4) {
|
||||
result += ",\n";
|
||||
}
|
||||
result += " {";
|
||||
result += "\"type\": \"file\", ";
|
||||
result += "\"name\": \"" + String(entry.name()) + "\", ";
|
||||
result += "\"size\": " + String(entry.size()) + ", ";
|
||||
result += "\"time\": " + String(entry.getLastWrite());
|
||||
result += "}";
|
||||
} // while
|
||||
|
||||
result += "\n]";
|
||||
server.sendHeader("Cache-Control", "no-cache");
|
||||
server.send(200, "text/javascript; charset=utf-8", result);
|
||||
} // handleListFiles()
|
||||
|
||||
// This function is called when the sysInfo service was requested.
|
||||
void handleSysInfo() {
|
||||
String result;
|
||||
|
||||
result += "{\n";
|
||||
result += " \"Chip Model\": " + String(ESP.getChipModel()) + ",\n";
|
||||
result += " \"Chip Cores\": " + String(ESP.getChipCores()) + ",\n";
|
||||
result += " \"Chip Revision\": " + String(ESP.getChipRevision()) + ",\n";
|
||||
result += " \"flashSize\": " + String(ESP.getFlashChipSize()) + ",\n";
|
||||
result += " \"freeHeap\": " + String(ESP.getFreeHeap()) + ",\n";
|
||||
|
||||
if (fsys == (fs::FS *)&FFat) {
|
||||
result += " \"fsTotalBytes\": " + String(FFat.totalBytes()) + ",\n";
|
||||
result += " \"fsUsedBytes\": " + String(FFat.usedBytes()) + ",\n";
|
||||
} else {
|
||||
result += " \"fsTotalBytes\": " + String(LittleFS.totalBytes()) + ",\n";
|
||||
result += " \"fsUsedBytes\": " + String(LittleFS.usedBytes()) + ",\n";
|
||||
}
|
||||
|
||||
result += "}";
|
||||
|
||||
server.sendHeader("Cache-Control", "no-cache");
|
||||
server.send(200, "text/javascript; charset=utf-8", result);
|
||||
} // handleSysInfo()
|
||||
|
||||
// ===== Request Handler class used to answer more complex requests =====
|
||||
|
||||
// The FileServerHandler is registered to the web server to support DELETE and UPLOAD of files into the filesystem.
|
||||
class FileServerHandler : public RequestHandler {
|
||||
public:
|
||||
// @brief Construct a new File Server Handler object
|
||||
// @param fs The file system to be used.
|
||||
// @param path Path to the root folder in the file system that is used for serving static data down and upload.
|
||||
// @param cache_header Cache Header to be used in replies.
|
||||
FileServerHandler() {
|
||||
TRACE("FileServerHandler is registered\n");
|
||||
}
|
||||
|
||||
// @brief check incoming request. Can handle POST for uploads and DELETE.
|
||||
// @param requestMethod method of the http request line.
|
||||
// @param requestUri request resource from the http request line.
|
||||
// @return true when method can be handled.
|
||||
bool canHandle(WebServer &server, HTTPMethod requestMethod, const String &uri) override {
|
||||
return ((requestMethod == HTTP_POST) || (requestMethod == HTTP_DELETE));
|
||||
} // canHandle()
|
||||
|
||||
bool canUpload(WebServer &server, const String &uri) override {
|
||||
// only allow upload on root fs level.
|
||||
return (uri == "/");
|
||||
} // canUpload()
|
||||
|
||||
bool handle(WebServer &server, HTTPMethod requestMethod, const String &requestUri) override {
|
||||
// ensure that filename starts with '/'
|
||||
String fName = requestUri;
|
||||
if (!fName.startsWith("/")) {
|
||||
fName = "/" + fName;
|
||||
}
|
||||
|
||||
if (requestMethod == HTTP_POST) {
|
||||
// all done in upload. no other forms.
|
||||
|
||||
} else if (requestMethod == HTTP_DELETE) {
|
||||
if (fsys->exists(fName)) {
|
||||
TRACE("DELETE %s\n", fName.c_str());
|
||||
fsys->remove(fName);
|
||||
}
|
||||
} // if
|
||||
|
||||
server.send(200); // all done.
|
||||
return (true);
|
||||
} // handle()
|
||||
|
||||
// uploading process
|
||||
void upload(WebServer UNUSED &server, const String &requestUri, HTTPUpload &upload) override {
|
||||
// ensure that filename starts with '/'
|
||||
static size_t uploadSize;
|
||||
|
||||
if (upload.status == UPLOAD_FILE_START) {
|
||||
String fName = upload.filename;
|
||||
|
||||
// Open the file for writing
|
||||
if (!fName.startsWith("/")) {
|
||||
fName = "/" + fName;
|
||||
}
|
||||
TRACE("start uploading file %s...\n", fName.c_str());
|
||||
|
||||
if (fsys->exists(fName)) {
|
||||
fsys->remove(fName);
|
||||
} // if
|
||||
_fsUploadFile = fsys->open(fName, "w");
|
||||
uploadSize = 0;
|
||||
|
||||
} else if (upload.status == UPLOAD_FILE_WRITE) {
|
||||
// Write received bytes
|
||||
if (_fsUploadFile) {
|
||||
size_t written = _fsUploadFile.write(upload.buf, upload.currentSize);
|
||||
if (written < upload.currentSize) {
|
||||
// upload failed
|
||||
TRACE(" write error!\n");
|
||||
_fsUploadFile.close();
|
||||
|
||||
// delete file to free up space in filesystem
|
||||
String fName = upload.filename;
|
||||
if (!fName.startsWith("/")) {
|
||||
fName = "/" + fName;
|
||||
}
|
||||
fsys->remove(fName);
|
||||
}
|
||||
uploadSize += upload.currentSize;
|
||||
// TRACE("free:: %d of %d\n", LittleFS.usedBytes(), LittleFS.totalBytes());
|
||||
// TRACE("written:: %d of %d\n", written, upload.currentSize);
|
||||
// TRACE("totalSize: %d\n", upload.currentSize + upload.totalSize);
|
||||
} // if
|
||||
|
||||
} else if (upload.status == UPLOAD_FILE_END) {
|
||||
TRACE("upload done.\n");
|
||||
// Close the file
|
||||
if (_fsUploadFile) {
|
||||
_fsUploadFile.close();
|
||||
TRACE(" %d bytes uploaded.\n", upload.totalSize);
|
||||
}
|
||||
} // if
|
||||
|
||||
} // upload()
|
||||
|
||||
protected:
|
||||
File _fsUploadFile;
|
||||
};
|
||||
|
||||
// Setup everything to make the webserver work.
|
||||
void setup(void) {
|
||||
delay(3000); // wait for serial monitor to start completely.
|
||||
|
||||
// Use Serial port for some trace information from the example
|
||||
Serial.begin(115200);
|
||||
Serial.setDebugOutput(false);
|
||||
|
||||
TRACE("Starting WebServer example...\n");
|
||||
|
||||
// ----- check partitions for finding the filesystem type -----
|
||||
esp_partition_iterator_t i;
|
||||
|
||||
i = esp_partition_find(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_FAT, nullptr);
|
||||
if (i) {
|
||||
TRACE("FAT partition found.");
|
||||
fsys = &FFat;
|
||||
|
||||
} else {
|
||||
i = esp_partition_find(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_SPIFFS, nullptr);
|
||||
if (i) {
|
||||
TRACE("SPIFFS partition found.");
|
||||
fsys = &LittleFS;
|
||||
|
||||
} else {
|
||||
TRACE("no partition found.");
|
||||
}
|
||||
}
|
||||
esp_partition_iterator_release(i);
|
||||
|
||||
// mount and format as needed
|
||||
TRACE("Mounting the filesystem...\n");
|
||||
if (!fsys) {
|
||||
TRACE("need to change the board configuration to include a partition for files.\n");
|
||||
delay(30000);
|
||||
|
||||
} else if ((fsys == (fs::FS *)&FFat) && (!FFat.begin())) {
|
||||
TRACE("could not mount the filesystem...\n");
|
||||
delay(2000);
|
||||
TRACE("formatting FAT...\n");
|
||||
FFat.format();
|
||||
delay(2000);
|
||||
TRACE("restarting...\n");
|
||||
delay(2000);
|
||||
ESP.restart();
|
||||
|
||||
} else if ((fsys == (fs::FS *)&LittleFS) && (!LittleFS.begin())) {
|
||||
TRACE("could not mount the filesystem...\n");
|
||||
delay(2000);
|
||||
TRACE("formatting LittleFS...\n");
|
||||
LittleFS.format();
|
||||
delay(2000);
|
||||
TRACE("restarting...\n");
|
||||
delay(2000);
|
||||
ESP.restart();
|
||||
}
|
||||
|
||||
// allow to address the device by the given name e.g. http://webserver
|
||||
WiFi.setHostname(HOSTNAME);
|
||||
|
||||
// start WiFI
|
||||
WiFi.mode(WIFI_STA);
|
||||
if (strlen(ssid) == 0) {
|
||||
WiFi.begin();
|
||||
} else {
|
||||
WiFi.begin(ssid, passPhrase);
|
||||
}
|
||||
|
||||
TRACE("Connect to WiFi...\n");
|
||||
while (WiFi.status() != WL_CONNECTED) {
|
||||
delay(500);
|
||||
TRACE(".");
|
||||
}
|
||||
TRACE("connected.\n");
|
||||
|
||||
// Ask for the current time using NTP request builtin into ESP firmware.
|
||||
TRACE("Setup ntp...\n");
|
||||
configTzTime(TIMEZONE, "pool.ntp.org");
|
||||
|
||||
TRACE("Register redirect...\n");
|
||||
|
||||
// register a redirect handler when only domain name is given.
|
||||
server.on("/", HTTP_GET, handleRedirect);
|
||||
|
||||
TRACE("Register service handlers...\n");
|
||||
|
||||
// serve a built-in htm page
|
||||
server.on("/$upload.htm", []() {
|
||||
server.send(200, "text/html", FPSTR(uploadContent));
|
||||
});
|
||||
|
||||
// register some REST services
|
||||
server.on("/api/list", HTTP_GET, handleListFiles);
|
||||
server.on("/api/sysinfo", HTTP_GET, handleSysInfo);
|
||||
|
||||
TRACE("Register file system handlers...\n");
|
||||
|
||||
// UPLOAD and DELETE of files in the file system using a request handler.
|
||||
server.addHandler(new FileServerHandler());
|
||||
|
||||
// enable CORS header in webserver results
|
||||
server.enableCORS(true);
|
||||
|
||||
// enable ETAG header in webserver results (used by serveStatic handler)
|
||||
#if defined(CUSTOM_ETAG_CALC)
|
||||
// This is a fast custom eTag generator. It returns a value based on the time the file was updated like
|
||||
// ETag: 63bbceb5
|
||||
server.enableETag(true, [](FS &fs, const String &path) -> String {
|
||||
File f = fs.open(path, "r");
|
||||
String eTag = String(f.getLastWrite(), 16); // use file modification timestamp to create ETag
|
||||
f.close();
|
||||
return (eTag);
|
||||
});
|
||||
|
||||
#else
|
||||
// enable standard ETAG calculation using md5 checksum of file content.
|
||||
server.enableETag(true);
|
||||
#endif
|
||||
|
||||
// serve all static files
|
||||
server.serveStatic("/", *fsys, "/");
|
||||
|
||||
TRACE("Register default (not found) answer...\n");
|
||||
|
||||
// handle cases when file is not found
|
||||
server.onNotFound([]() {
|
||||
// standard not found in browser.
|
||||
server.send(404, "text/html", FPSTR(notFoundContent));
|
||||
});
|
||||
|
||||
server.begin();
|
||||
|
||||
TRACE("open <http://%s> or <http://%s>\n", WiFi.getHostname(), WiFi.localIP().toString().c_str());
|
||||
} // setup
|
||||
|
||||
// run the server...
|
||||
void loop(void) {
|
||||
server.handleClient();
|
||||
} // loop()
|
||||
|
||||
// end.
|
||||
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* @file builtinfiles.h
|
||||
* @brief This file is part of the WebServer example for the ESP8266WebServer.
|
||||
*
|
||||
* This file contains long, multiline text variables for all builtin resources.
|
||||
*/
|
||||
|
||||
// used for $upload.htm
|
||||
static const char uploadContent[] PROGMEM =
|
||||
R"==(
|
||||
<!doctype html>
|
||||
<html lang='en'>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Upload</title>
|
||||
</head>
|
||||
|
||||
<body style="width:300px">
|
||||
<h1>Upload</h1>
|
||||
<div><a href="/">Home</a></div>
|
||||
<hr>
|
||||
<div id='zone' style='width:16em;height:12em;padding:10px;background-color:#ddd'>Drop files here...</div>
|
||||
|
||||
<script>
|
||||
// allow drag&drop of file objects
|
||||
function dragHelper(e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
// allow drag&drop of file objects
|
||||
function dropped(e) {
|
||||
dragHelper(e);
|
||||
var fls = e.dataTransfer.files;
|
||||
var formData = new FormData();
|
||||
for (var i = 0; i < fls.length; i++) {
|
||||
formData.append('file', fls[i], '/' + fls[i].name);
|
||||
}
|
||||
fetch('/', { method: 'POST', body: formData }).then(function () {
|
||||
window.alert('done.');
|
||||
});
|
||||
}
|
||||
var z = document.getElementById('zone');
|
||||
z.addEventListener('dragenter', dragHelper, false);
|
||||
z.addEventListener('dragover', dragHelper, false);
|
||||
z.addEventListener('drop', dropped, false);
|
||||
</script>
|
||||
</body>
|
||||
)==";
|
||||
|
||||
// used for $upload.htm
|
||||
static const char notFoundContent[] PROGMEM = R"==(
|
||||
<html>
|
||||
<head>
|
||||
<title>Resource not found</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>The resource was not found.</p>
|
||||
<p><a href="/">Start again</a></p>
|
||||
</body>
|
||||
)==";
|
||||
@@ -0,0 +1,3 @@
|
||||
requires_any:
|
||||
- CONFIG_SOC_WIFI_SUPPORTED=y
|
||||
- CONFIG_ESP_WIFI_REMOTE_ENABLED=y
|
||||
@@ -0,0 +1,65 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Files</title>
|
||||
<link Content-Type="text/css" href="/style.css" rel="stylesheet" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Files on Server</h1>
|
||||
|
||||
<p>These files are available on the server to be opened or delete:</p>
|
||||
<div id="list">
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// load and display all files after page loading has finished
|
||||
window.addEventListener("load", function () {
|
||||
fetch('/api/list')
|
||||
.then(function (result) { return result.json(); })
|
||||
.then(function (e) {
|
||||
var listObj = document.querySelector('#list');
|
||||
e.forEach(function (f) {
|
||||
var entry = document.createElement("div");
|
||||
var nameObj = document.createElement("a");
|
||||
nameObj.href = '/' + f.name;
|
||||
nameObj.innerText = '/' + f.name;
|
||||
entry.appendChild(nameObj)
|
||||
|
||||
entry.appendChild(document.createTextNode(' (' + f.size + ') '));
|
||||
|
||||
var timeObj = document.createElement("span");
|
||||
timeObj.innerText = (new Date(f.time*1000)).toLocaleString();
|
||||
entry.appendChild(timeObj)
|
||||
entry.appendChild(document.createTextNode(" "));
|
||||
|
||||
var delObj = document.createElement("span");
|
||||
delObj.className = 'deleteFile';
|
||||
delObj.innerText = ' [delete]';
|
||||
entry.appendChild(delObj)
|
||||
|
||||
listObj.appendChild(entry)
|
||||
});
|
||||
|
||||
})
|
||||
.catch(function (err) {
|
||||
window.alert(err);
|
||||
});
|
||||
});
|
||||
|
||||
window.addEventListener("click", function (evt) {
|
||||
var t = evt.target;
|
||||
if (t.className === 'deleteFile') {
|
||||
var fname = t.parentElement.innerText;
|
||||
fname = fname.split(' ')[0];
|
||||
if (window.confirm("Delete " + fname + " ?")) {
|
||||
fetch(fname, { method: 'DELETE' });
|
||||
document.location.reload(false);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,25 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>HomePage</title>
|
||||
<link Content-Type="text/css" href="/style.css" rel="stylesheet" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Homepage of the WebServer Example</h1>
|
||||
|
||||
<p>The following pages are available:</p>
|
||||
<ul>
|
||||
<li><a href="/index.htm">/index.htm</a> - This page</li>
|
||||
<li><a href="/files.htm">/files.htm</a> - Manage files on the server</li>
|
||||
<li><a href="/$upload.htm">/$upload.htm</a> - Built-in upload utility</a></li>
|
||||
<li><a href="/none.htm">/none.htm</a> - See the default response when files are not found.</a></li>
|
||||
</ul>
|
||||
|
||||
<p>The following REST services are available:</p>
|
||||
<ul>
|
||||
<li><a href="/api/sysinfo">/api/sysinfo</a> - Some system level information</a></li>
|
||||
<li><a href="/api/list">/api/list</a> - Array of all files</a></li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,9 @@
|
||||
html, body {
|
||||
color: #111111; font-family: Arial, ui-sans-serif, sans-serif; font-size: 1em; background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
#list > div {
|
||||
margin: 0 0 0.5rem 0;
|
||||
}
|
||||
|
||||
a { color: inherit; cursor: pointer; }
|
||||
@@ -0,0 +1,13 @@
|
||||
// Secrets for your local home network
|
||||
|
||||
// This is a "hard way" to configure your local WiFi network name and passphrase
|
||||
// into the source code and the uploaded sketch.
|
||||
//
|
||||
// Using the WiFi Manager is preferred and avoids reprogramming when your network changes.
|
||||
// See https://homeding.github.io/#page=/wifimanager.md
|
||||
|
||||
// ssid and passPhrase can be used when compiling for a specific environment as a 2. option.
|
||||
|
||||
// add you wifi network name and PassPhrase or use WiFi Manager
|
||||
const char *ssid = "";
|
||||
const char *passPhrase = "";
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
To upload through terminal you can use: curl -F "image=@firmware.bin" esp8266-webupdate.local/update
|
||||
*/
|
||||
|
||||
#include <WiFi.h>
|
||||
#include <NetworkClient.h>
|
||||
#include <WebServer.h>
|
||||
#include <ESPmDNS.h>
|
||||
#include <Update.h>
|
||||
|
||||
const char *host = "esp32-webupdate";
|
||||
const char *ssid = "........";
|
||||
const char *password = "........";
|
||||
|
||||
// Set the username and password for firmware upload
|
||||
const char *authUser = "........";
|
||||
const char *authPass = "........";
|
||||
|
||||
WebServer server(80);
|
||||
const char *serverIndex =
|
||||
"<form method='POST' action='/update' enctype='multipart/form-data'><input type='file' name='update'><input type='submit' value='Update'></form>";
|
||||
|
||||
const char *csrfHeaders[2] = {"Origin", "Host"};
|
||||
static bool authenticated = false;
|
||||
|
||||
void setup(void) {
|
||||
Serial.begin(115200);
|
||||
Serial.println();
|
||||
Serial.println("Booting Sketch...");
|
||||
WiFi.mode(WIFI_AP_STA);
|
||||
WiFi.begin(ssid, password);
|
||||
if (WiFi.waitForConnectResult() == WL_CONNECTED) {
|
||||
MDNS.begin(host);
|
||||
server.collectHeaders(csrfHeaders, 2);
|
||||
server.on("/", HTTP_GET, []() {
|
||||
if (!server.authenticate(authUser, authPass)) {
|
||||
return server.requestAuthentication();
|
||||
}
|
||||
server.sendHeader("Connection", "close");
|
||||
server.send(200, "text/html", serverIndex);
|
||||
});
|
||||
server.on(
|
||||
"/update", HTTP_POST,
|
||||
[]() {
|
||||
if (!authenticated) {
|
||||
return server.requestAuthentication();
|
||||
}
|
||||
server.sendHeader("Connection", "close");
|
||||
if (Update.hasError()) {
|
||||
server.send(200, "text/plain", "FAIL");
|
||||
} else {
|
||||
server.send(200, "text/plain", "Success! Rebooting...");
|
||||
delay(500);
|
||||
ESP.restart();
|
||||
}
|
||||
},
|
||||
[]() {
|
||||
HTTPUpload &upload = server.upload();
|
||||
if (upload.status == UPLOAD_FILE_START) {
|
||||
Serial.setDebugOutput(true);
|
||||
authenticated = server.authenticate(authUser, authPass);
|
||||
if (!authenticated) {
|
||||
Serial.println("Authentication fail!");
|
||||
return;
|
||||
}
|
||||
String origin = server.header(String(csrfHeaders[0]));
|
||||
String host = server.header(String(csrfHeaders[1]));
|
||||
String expectedOrigin = String("http://") + host;
|
||||
if (origin != expectedOrigin) {
|
||||
Serial.printf("Wrong origin received! Expected: %s, Received: %s\n", expectedOrigin.c_str(), origin.c_str());
|
||||
authenticated = false;
|
||||
return;
|
||||
}
|
||||
|
||||
Serial.printf("Update: %s\n", upload.filename.c_str());
|
||||
if (!Update.begin()) { //start with max available size
|
||||
Update.printError(Serial);
|
||||
}
|
||||
} else if (authenticated && upload.status == UPLOAD_FILE_WRITE) {
|
||||
if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
|
||||
Update.printError(Serial);
|
||||
}
|
||||
} else if (authenticated && upload.status == UPLOAD_FILE_END) {
|
||||
if (Update.end(true)) { //true to set the size to the current progress
|
||||
Serial.printf("Update Success: %zu\nRebooting...\n", upload.totalSize);
|
||||
} else {
|
||||
Update.printError(Serial);
|
||||
}
|
||||
Serial.setDebugOutput(false);
|
||||
} else if (authenticated) {
|
||||
Serial.printf("Update Failed Unexpectedly (likely broken connection): status=%d\n", upload.status);
|
||||
}
|
||||
}
|
||||
);
|
||||
server.begin();
|
||||
MDNS.addService("http", "tcp", 80);
|
||||
|
||||
Serial.printf("Ready! Open http://%s.local in your browser\n", host);
|
||||
} else {
|
||||
Serial.println("WiFi Failed");
|
||||
}
|
||||
}
|
||||
|
||||
void loop(void) {
|
||||
server.handleClient();
|
||||
delay(2); //allow the cpu to switch to other tasks
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
requires_any:
|
||||
- CONFIG_SOC_WIFI_SUPPORTED=y
|
||||
- CONFIG_ESP_WIFI_REMOTE_ENABLED=y
|
||||
@@ -0,0 +1,38 @@
|
||||
#######################################
|
||||
# Syntax Coloring Map For Ultrasound
|
||||
#######################################
|
||||
|
||||
#######################################
|
||||
# Datatypes (KEYWORD1)
|
||||
#######################################
|
||||
|
||||
WebServer KEYWORD1
|
||||
WebServerSecure KEYWORD1
|
||||
HTTPMethod KEYWORD1
|
||||
|
||||
#######################################
|
||||
# Methods and Functions (KEYWORD2)
|
||||
#######################################
|
||||
|
||||
begin KEYWORD2
|
||||
handleClient KEYWORD2
|
||||
on KEYWORD2
|
||||
addHandler KEYWORD2
|
||||
uri KEYWORD2
|
||||
method KEYWORD2
|
||||
client KEYWORD2
|
||||
send KEYWORD2
|
||||
arg KEYWORD2
|
||||
argName KEYWORD2
|
||||
args KEYWORD2
|
||||
hasArg KEYWORD2
|
||||
onNotFound KEYWORD2
|
||||
|
||||
#######################################
|
||||
# Constants (LITERAL1)
|
||||
#######################################
|
||||
|
||||
HTTP_GET LITERAL1
|
||||
HTTP_POST LITERAL1
|
||||
HTTP_ANY LITERAL1
|
||||
CONTENT_LENGTH_UNKNOWN LITERAL1
|
||||
@@ -0,0 +1,9 @@
|
||||
name=WebServer
|
||||
version=3.3.7
|
||||
author=Ivan Grokhotkov
|
||||
maintainer=Ivan Grokhtkov <ivan@esp8266.com>
|
||||
sentence=Simple web server library
|
||||
paragraph=The library supports HTTP GET and POST requests, provides argument parsing, handles one client at a time.
|
||||
category=Communication
|
||||
url=
|
||||
architectures=esp32
|
||||
@@ -0,0 +1,9 @@
|
||||
#ifndef _HTTP_Method_H_
|
||||
#define _HTTP_Method_H_
|
||||
|
||||
#include "http_parser.h"
|
||||
|
||||
typedef enum http_method HTTPMethod;
|
||||
#define HTTP_ANY (HTTPMethod)(255)
|
||||
|
||||
#endif /* _HTTP_Method_H_ */
|
||||
@@ -0,0 +1,66 @@
|
||||
#ifndef MIDDLEWARES_H
|
||||
#define MIDDLEWARES_H
|
||||
|
||||
#include <WebServer.h>
|
||||
#include <Stream.h>
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
// curl-like logging middleware
|
||||
class LoggingMiddleware : public Middleware {
|
||||
public:
|
||||
void setOutput(Print &output);
|
||||
|
||||
bool run(WebServer &server, Middleware::Callback next) override;
|
||||
|
||||
private:
|
||||
Print *_out = nullptr;
|
||||
};
|
||||
|
||||
class CorsMiddleware : public Middleware {
|
||||
public:
|
||||
CorsMiddleware &setOrigin(const char *origin);
|
||||
CorsMiddleware &setMethods(const char *methods);
|
||||
CorsMiddleware &setHeaders(const char *headers);
|
||||
CorsMiddleware &setAllowCredentials(bool credentials);
|
||||
CorsMiddleware &setMaxAge(uint32_t seconds);
|
||||
|
||||
void addCORSHeaders(WebServer &server);
|
||||
|
||||
bool run(WebServer &server, Middleware::Callback next) override;
|
||||
|
||||
private:
|
||||
String _origin = F("*");
|
||||
String _methods = F("*");
|
||||
String _headers = F("*");
|
||||
bool _credentials = true;
|
||||
uint32_t _maxAge = 86400;
|
||||
};
|
||||
|
||||
class AuthenticationMiddleware : public Middleware {
|
||||
public:
|
||||
AuthenticationMiddleware &setUsername(const char *username);
|
||||
AuthenticationMiddleware &setPassword(const char *password);
|
||||
AuthenticationMiddleware &setPasswordHash(const char *sha1AsBase64orHex);
|
||||
AuthenticationMiddleware &setCallback(WebServer::THandlerFunctionAuthCheck fn);
|
||||
|
||||
AuthenticationMiddleware &setRealm(const char *realm);
|
||||
AuthenticationMiddleware &setAuthMethod(HTTPAuthMethod method);
|
||||
AuthenticationMiddleware &setAuthFailureMessage(const char *message);
|
||||
|
||||
bool isAllowed(WebServer &server) const;
|
||||
|
||||
bool run(WebServer &server, Middleware::Callback next) override;
|
||||
|
||||
private:
|
||||
String _username;
|
||||
String _password;
|
||||
bool _hash = false;
|
||||
WebServer::THandlerFunctionAuthCheck _callback;
|
||||
|
||||
const char *_realm = nullptr;
|
||||
HTTPAuthMethod _method = BASIC_AUTH;
|
||||
String _authFailMsg;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,622 @@
|
||||
/*
|
||||
Parsing.cpp - HTTP request parsing.
|
||||
|
||||
Copyright (c) 2015 Ivan Grokhotkov. All rights reserved.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling)
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <esp32-hal-log.h>
|
||||
#include "NetworkServer.h"
|
||||
#include "NetworkClient.h"
|
||||
#include "WebServer.h"
|
||||
#include "detail/mimetable.h"
|
||||
|
||||
#ifndef WEBSERVER_MAX_POST_ARGS
|
||||
#define WEBSERVER_MAX_POST_ARGS 32
|
||||
#endif
|
||||
|
||||
#define __STR(a) #a
|
||||
#define _STR(a) __STR(a)
|
||||
static const char *_http_method_str[] = {
|
||||
#define XX(num, name, string) _STR(name),
|
||||
HTTP_METHOD_MAP(XX)
|
||||
#undef XX
|
||||
};
|
||||
|
||||
static const char Content_Type[] PROGMEM = "Content-Type";
|
||||
static const char filename[] PROGMEM = "filename";
|
||||
|
||||
static char *readBytesWithTimeout(NetworkClient &client, size_t maxLength, size_t &dataLength, int timeout_ms) {
|
||||
char *buf = nullptr;
|
||||
dataLength = 0;
|
||||
while (dataLength < maxLength) {
|
||||
int tries = timeout_ms;
|
||||
size_t newLength;
|
||||
while (!(newLength = client.available()) && tries--) {
|
||||
delay(1);
|
||||
}
|
||||
if (!newLength) {
|
||||
break;
|
||||
}
|
||||
if (!buf) {
|
||||
buf = (char *)malloc(newLength + 1);
|
||||
if (!buf) {
|
||||
return nullptr;
|
||||
}
|
||||
} else {
|
||||
char *newBuf = (char *)realloc(buf, dataLength + newLength + 1);
|
||||
if (!newBuf) {
|
||||
free(buf);
|
||||
return nullptr;
|
||||
}
|
||||
buf = newBuf;
|
||||
}
|
||||
client.readBytes(buf + dataLength, newLength);
|
||||
dataLength += newLength;
|
||||
buf[dataLength] = '\0';
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
bool WebServer::_parseRequest(NetworkClient &client) {
|
||||
// Read the first line of HTTP request
|
||||
String req = client.readStringUntil('\r');
|
||||
client.readStringUntil('\n');
|
||||
//reset header value
|
||||
if (_collectAllHeaders) {
|
||||
// clear previous headers
|
||||
collectAllHeaders();
|
||||
} else {
|
||||
// clear previous headers
|
||||
for (RequestArgument *header = _currentHeaders; header; header = header->next) {
|
||||
header->value = String();
|
||||
}
|
||||
}
|
||||
|
||||
// First line of HTTP request looks like "GET /path HTTP/1.1"
|
||||
// Retrieve the "/path" part by finding the spaces
|
||||
int addr_start = req.indexOf(' ');
|
||||
int addr_end = req.indexOf(' ', addr_start + 1);
|
||||
if (addr_start == -1 || addr_end == -1) {
|
||||
log_e("Invalid request: %s", req.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
String methodStr = req.substring(0, addr_start);
|
||||
String url = req.substring(addr_start + 1, addr_end);
|
||||
String versionEnd = req.substring(addr_end + 8);
|
||||
_currentVersion = atoi(versionEnd.c_str());
|
||||
String searchStr = "";
|
||||
int hasSearch = url.indexOf('?');
|
||||
if (hasSearch != -1) {
|
||||
searchStr = url.substring(hasSearch + 1);
|
||||
url = url.substring(0, hasSearch);
|
||||
}
|
||||
_currentUri = url;
|
||||
_chunked = false;
|
||||
_clientContentLength = 0; // not known yet, or invalid
|
||||
|
||||
HTTPMethod method = HTTP_ANY;
|
||||
size_t num_methods = sizeof(_http_method_str) / sizeof(const char *);
|
||||
for (size_t i = 0; i < num_methods; i++) {
|
||||
if (methodStr == _http_method_str[i]) {
|
||||
method = (HTTPMethod)i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (method == HTTP_ANY) {
|
||||
log_e("Unknown HTTP Method: %s", methodStr.c_str());
|
||||
return false;
|
||||
}
|
||||
_currentMethod = method;
|
||||
|
||||
log_v("method: %s url: %s search: %s", methodStr.c_str(), url.c_str(), searchStr.c_str());
|
||||
|
||||
//attach handler
|
||||
RequestHandler *handler;
|
||||
for (handler = _firstHandler; handler; handler = handler->next()) {
|
||||
if (handler->canHandle(*this, _currentMethod, _currentUri)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
_currentHandler = handler;
|
||||
|
||||
String formData;
|
||||
// below is needed only when POST type request
|
||||
if (method == HTTP_POST || method == HTTP_PUT || method == HTTP_PATCH || method == HTTP_DELETE) {
|
||||
String boundaryStr;
|
||||
String headerName;
|
||||
String headerValue;
|
||||
bool isForm = false;
|
||||
bool isEncoded = false;
|
||||
//parse headers
|
||||
while (1) {
|
||||
req = client.readStringUntil('\r');
|
||||
client.readStringUntil('\n');
|
||||
if (req == "") {
|
||||
break; //no moar headers
|
||||
}
|
||||
int headerDiv = req.indexOf(':');
|
||||
if (headerDiv == -1) {
|
||||
break;
|
||||
}
|
||||
headerName = req.substring(0, headerDiv);
|
||||
headerValue = req.substring(headerDiv + 1);
|
||||
headerValue.trim();
|
||||
_collectHeader(headerName.c_str(), headerValue.c_str());
|
||||
|
||||
if (headerName.equalsIgnoreCase(FPSTR(Content_Type))) {
|
||||
using namespace mime;
|
||||
if (headerValue.startsWith(FPSTR(mimeTable[txt].mimeType))) {
|
||||
isForm = false;
|
||||
} else if (headerValue.startsWith(F("application/x-www-form-urlencoded"))) {
|
||||
isForm = false;
|
||||
isEncoded = true;
|
||||
} else if (headerValue.startsWith(F("multipart/"))) {
|
||||
boundaryStr = headerValue.substring(headerValue.indexOf('=') + 1);
|
||||
boundaryStr.replace("\"", "");
|
||||
isForm = true;
|
||||
}
|
||||
} else if (headerName.equalsIgnoreCase(F("Content-Length"))) {
|
||||
_clientContentLength = headerValue.toInt();
|
||||
} else if (headerName.equalsIgnoreCase(F("Host"))) {
|
||||
_hostHeader = headerValue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isForm && _currentHandler && _currentHandler->canRaw(*this, _currentUri)) {
|
||||
log_v("Parse raw");
|
||||
_currentRaw.reset(new HTTPRaw());
|
||||
_currentRaw->status = RAW_START;
|
||||
_currentRaw->totalSize = 0;
|
||||
_currentRaw->currentSize = 0;
|
||||
log_v("Start Raw");
|
||||
_currentHandler->raw(*this, _currentUri, *_currentRaw);
|
||||
_currentRaw->status = RAW_WRITE;
|
||||
|
||||
while (_currentRaw->totalSize < (size_t)_clientContentLength) {
|
||||
size_t read_len = std::min((size_t)_clientContentLength - _currentRaw->totalSize, (size_t)HTTP_RAW_BUFLEN);
|
||||
_currentRaw->currentSize = client.readBytes(_currentRaw->buf, read_len);
|
||||
_currentRaw->totalSize += _currentRaw->currentSize;
|
||||
if (_currentRaw->currentSize == 0) {
|
||||
_currentRaw->status = RAW_ABORTED;
|
||||
_currentHandler->raw(*this, _currentUri, *_currentRaw);
|
||||
return false;
|
||||
}
|
||||
_currentHandler->raw(*this, _currentUri, *_currentRaw);
|
||||
}
|
||||
_currentRaw->status = RAW_END;
|
||||
_currentHandler->raw(*this, _currentUri, *_currentRaw);
|
||||
log_v("Finish Raw");
|
||||
} else if (!isForm) {
|
||||
size_t plainLength;
|
||||
char *plainBuf = readBytesWithTimeout(client, _clientContentLength, plainLength, HTTP_MAX_POST_WAIT);
|
||||
if (plainLength < (size_t)_clientContentLength) {
|
||||
free(plainBuf);
|
||||
return false;
|
||||
}
|
||||
if (_clientContentLength > 0) {
|
||||
if (isEncoded) {
|
||||
//url encoded form
|
||||
if (searchStr != "") {
|
||||
searchStr += '&';
|
||||
}
|
||||
searchStr += plainBuf;
|
||||
}
|
||||
_parseArguments(searchStr);
|
||||
if (!isEncoded) {
|
||||
//plain post json or other data
|
||||
RequestArgument &arg = _currentArgs[_currentArgCount++];
|
||||
arg.key = F("plain");
|
||||
arg.value = String(plainBuf);
|
||||
}
|
||||
|
||||
log_v("Plain: %s", plainBuf);
|
||||
free(plainBuf);
|
||||
} else {
|
||||
// No content - but we can still have arguments in the URL.
|
||||
_parseArguments(searchStr);
|
||||
}
|
||||
} else {
|
||||
// it IS a form
|
||||
_parseArguments(searchStr);
|
||||
if (!_parseForm(client, boundaryStr, _clientContentLength)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
String headerName;
|
||||
String headerValue;
|
||||
//parse headers
|
||||
while (1) {
|
||||
req = client.readStringUntil('\r');
|
||||
client.readStringUntil('\n');
|
||||
if (req == "") {
|
||||
break; //no moar headers
|
||||
}
|
||||
int headerDiv = req.indexOf(':');
|
||||
if (headerDiv == -1) {
|
||||
break;
|
||||
}
|
||||
headerName = req.substring(0, headerDiv);
|
||||
headerValue = req.substring(headerDiv + 2);
|
||||
_collectHeader(headerName.c_str(), headerValue.c_str());
|
||||
|
||||
if (headerName.equalsIgnoreCase("Host")) {
|
||||
_hostHeader = headerValue;
|
||||
}
|
||||
}
|
||||
_parseArguments(searchStr);
|
||||
}
|
||||
client.clear();
|
||||
|
||||
log_v("Request: %s", url.c_str());
|
||||
log_v(" Arguments: %s", searchStr.c_str());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WebServer::_collectHeader(const char *headerName, const char *headerValue) {
|
||||
RequestArgument *last = nullptr;
|
||||
for (RequestArgument *header = _currentHeaders; header; header = header->next) {
|
||||
if (header->next == nullptr) {
|
||||
last = header;
|
||||
}
|
||||
if (header->key.equalsIgnoreCase(headerName)) {
|
||||
header->value = headerValue;
|
||||
log_v("header collected: %s: %s", headerName, headerValue);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
assert(last);
|
||||
if (_collectAllHeaders) {
|
||||
last->next = new RequestArgument();
|
||||
last->next->key = headerName;
|
||||
last->next->value = headerValue;
|
||||
_headerKeysCount++;
|
||||
log_v("header collected: %s: %s", headerName, headerValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
log_v("header skipped: %s: %s", headerName, headerValue);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void WebServer::_parseArguments(const String &data) {
|
||||
log_v("args: %s", data.c_str());
|
||||
if (_currentArgs) {
|
||||
delete[] _currentArgs;
|
||||
}
|
||||
_currentArgs = 0;
|
||||
if (data.length() == 0) {
|
||||
_currentArgCount = 0;
|
||||
_currentArgs = new RequestArgument[1];
|
||||
return;
|
||||
}
|
||||
_currentArgCount = 1;
|
||||
|
||||
for (int i = 0; i < (int)data.length();) {
|
||||
i = data.indexOf('&', i);
|
||||
if (i == -1) {
|
||||
break;
|
||||
}
|
||||
++i;
|
||||
++_currentArgCount;
|
||||
}
|
||||
log_v("args count: %d", _currentArgCount);
|
||||
|
||||
_currentArgs = new RequestArgument[_currentArgCount + 1];
|
||||
int pos = 0;
|
||||
int iarg;
|
||||
for (iarg = 0; iarg < _currentArgCount;) {
|
||||
int equal_sign_index = data.indexOf('=', pos);
|
||||
int next_arg_index = data.indexOf('&', pos);
|
||||
log_v("pos %d =@%d &@%d", pos, equal_sign_index, next_arg_index);
|
||||
if ((equal_sign_index == -1) || ((equal_sign_index > next_arg_index) && (next_arg_index != -1))) {
|
||||
log_e("arg missing value: %d", iarg);
|
||||
if (next_arg_index == -1) {
|
||||
break;
|
||||
}
|
||||
pos = next_arg_index + 1;
|
||||
continue;
|
||||
}
|
||||
RequestArgument &arg = _currentArgs[iarg];
|
||||
arg.key = urlDecode(data.substring(pos, equal_sign_index));
|
||||
arg.value = urlDecode(data.substring(equal_sign_index + 1, next_arg_index));
|
||||
log_v("arg %d key: %s value: %s", iarg, arg.key.c_str(), arg.value.c_str());
|
||||
++iarg;
|
||||
if (next_arg_index == -1) {
|
||||
break;
|
||||
}
|
||||
pos = next_arg_index + 1;
|
||||
}
|
||||
_currentArgCount = iarg;
|
||||
log_v("args count: %d", _currentArgCount);
|
||||
}
|
||||
|
||||
void WebServer::_uploadWriteByte(uint8_t b) {
|
||||
if (_currentUpload->currentSize == HTTP_UPLOAD_BUFLEN) {
|
||||
if (_currentHandler && _currentHandler->canUpload(*this, _currentUri)) {
|
||||
_currentHandler->upload(*this, _currentUri, *_currentUpload);
|
||||
}
|
||||
_currentUpload->totalSize += _currentUpload->currentSize;
|
||||
_currentUpload->currentSize = 0;
|
||||
}
|
||||
_currentUpload->buf[_currentUpload->currentSize++] = b;
|
||||
}
|
||||
|
||||
int WebServer::_uploadReadByte(NetworkClient &client) {
|
||||
int res = client.read();
|
||||
|
||||
if (res < 0) {
|
||||
// keep trying until you either read a valid byte or timeout
|
||||
const unsigned long startMillis = millis();
|
||||
const long timeoutIntervalMillis = client.getTimeout();
|
||||
bool timedOut = false;
|
||||
for (;;) {
|
||||
if (!client.connected()) {
|
||||
return -1;
|
||||
}
|
||||
// loosely modeled after blinkWithoutDelay pattern
|
||||
while (!timedOut && !client.available() && client.connected()) {
|
||||
delay(2);
|
||||
timedOut = (millis() - startMillis) >= timeoutIntervalMillis;
|
||||
}
|
||||
|
||||
res = client.read();
|
||||
if (res >= 0) {
|
||||
return res; // exit on a valid read
|
||||
}
|
||||
// NOTE: it is possible to get here and have all of the following
|
||||
// assertions hold true
|
||||
//
|
||||
// -- client.available() > 0
|
||||
// -- client.connected == true
|
||||
// -- res == -1
|
||||
//
|
||||
// a simple retry strategy overcomes this which is to say the
|
||||
// assertion is not permanent, but the reason that this works
|
||||
// is elusive, and possibly indicative of a more subtle underlying
|
||||
// issue
|
||||
|
||||
timedOut = (millis() - startMillis) >= timeoutIntervalMillis;
|
||||
if (timedOut) {
|
||||
return res; // exit on a timeout
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
bool WebServer::_parseForm(NetworkClient &client, const String &boundary, uint32_t len) {
|
||||
(void)len;
|
||||
log_v("Parse Form: Boundary: %s Length: %u", boundary.c_str(), len);
|
||||
String line;
|
||||
int retry = 0;
|
||||
do {
|
||||
line = client.readStringUntil('\r');
|
||||
++retry;
|
||||
} while (line.length() == 0 && retry < 3);
|
||||
|
||||
client.readStringUntil('\n');
|
||||
//start reading the form
|
||||
if (line == ("--" + boundary)) {
|
||||
if (_postArgs) {
|
||||
delete[] _postArgs;
|
||||
}
|
||||
_postArgs = new RequestArgument[WEBSERVER_MAX_POST_ARGS];
|
||||
_postArgsLen = 0;
|
||||
while (1) {
|
||||
String argName;
|
||||
String argValue;
|
||||
String argType;
|
||||
String argFilename;
|
||||
bool argIsFile = false;
|
||||
|
||||
line = client.readStringUntil('\r');
|
||||
client.readStringUntil('\n');
|
||||
if (line.length() > (size_t)19 && line.substring(0, 19).equalsIgnoreCase(F("Content-Disposition"))) {
|
||||
int nameStart = line.indexOf('=');
|
||||
if (nameStart != -1) {
|
||||
argName = line.substring(nameStart + 2);
|
||||
nameStart = argName.indexOf('=');
|
||||
if (nameStart == -1) {
|
||||
argName = argName.substring(0, argName.length() - 1);
|
||||
} else {
|
||||
argFilename = argName.substring(nameStart + 2, argName.length() - 1);
|
||||
argName = argName.substring(0, argName.indexOf('"'));
|
||||
argIsFile = true;
|
||||
log_v("PostArg FileName: %s", argFilename.c_str());
|
||||
//use GET to set the filename if uploading using blob
|
||||
if (argFilename == F("blob") && hasArg(FPSTR(filename))) {
|
||||
argFilename = arg(FPSTR(filename));
|
||||
}
|
||||
}
|
||||
log_v("PostArg Name: %s", argName.c_str());
|
||||
using namespace mime;
|
||||
argType = FPSTR(mimeTable[txt].mimeType);
|
||||
line = client.readStringUntil('\r');
|
||||
client.readStringUntil('\n');
|
||||
while (line.length() > 0) {
|
||||
if (line.length() > (size_t)12 && line.substring(0, 12).equalsIgnoreCase(FPSTR(Content_Type))) {
|
||||
argType = line.substring(line.indexOf(':') + 2);
|
||||
}
|
||||
//skip over any other headers
|
||||
line = client.readStringUntil('\r');
|
||||
client.readStringUntil('\n');
|
||||
}
|
||||
log_v("PostArg Type: %s", argType.c_str());
|
||||
if (!argIsFile) {
|
||||
while (1) {
|
||||
line = client.readStringUntil('\r');
|
||||
client.readStringUntil('\n');
|
||||
if (line.startsWith("--" + boundary)) {
|
||||
break;
|
||||
}
|
||||
if (argValue.length() > (size_t)0) {
|
||||
argValue += "\n";
|
||||
}
|
||||
argValue += line;
|
||||
}
|
||||
log_v("PostArg Value: %s", argValue.c_str());
|
||||
|
||||
RequestArgument &arg = _postArgs[_postArgsLen++];
|
||||
arg.key = argName;
|
||||
arg.value = argValue;
|
||||
|
||||
if (line == ("--" + boundary + "--")) {
|
||||
log_v("Done Parsing POST");
|
||||
break;
|
||||
} else if (_postArgsLen >= WEBSERVER_MAX_POST_ARGS) {
|
||||
log_e("Too many PostArgs (max: %d) in request.", WEBSERVER_MAX_POST_ARGS);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
_currentUpload.reset(new HTTPUpload());
|
||||
_currentUpload->status = UPLOAD_FILE_START;
|
||||
_currentUpload->name = argName;
|
||||
_currentUpload->filename = argFilename;
|
||||
_currentUpload->type = argType;
|
||||
_currentUpload->totalSize = 0;
|
||||
_currentUpload->currentSize = 0;
|
||||
log_v("Start File: %s Type: %s", _currentUpload->filename.c_str(), _currentUpload->type.c_str());
|
||||
if (_currentHandler && _currentHandler->canUpload(*this, _currentUri)) {
|
||||
_currentHandler->upload(*this, _currentUri, *_currentUpload);
|
||||
}
|
||||
_currentUpload->status = UPLOAD_FILE_WRITE;
|
||||
|
||||
int fastBoundaryLen = 4 /* \r\n-- */ + boundary.length() + 1 /* \0 */;
|
||||
char fastBoundary[fastBoundaryLen];
|
||||
snprintf(fastBoundary, fastBoundaryLen, "\r\n--%s", boundary.c_str());
|
||||
int boundaryPtr = 0;
|
||||
while (true) {
|
||||
int ret = _uploadReadByte(client);
|
||||
if (ret < 0) {
|
||||
// Unexpected, we should have had data available per above
|
||||
return _parseFormUploadAborted();
|
||||
}
|
||||
char in = (char)ret;
|
||||
if (in == fastBoundary[boundaryPtr]) {
|
||||
// The input matched the current expected character, advance and possibly exit this file
|
||||
boundaryPtr++;
|
||||
if (boundaryPtr == fastBoundaryLen - 1) {
|
||||
// We read the whole boundary line, we're done here!
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// The char doesn't match what we want, so dump whatever matches we had, the read in char, and reset ptr to start
|
||||
for (int i = 0; i < boundaryPtr; i++) {
|
||||
_uploadWriteByte(fastBoundary[i]);
|
||||
}
|
||||
if (in == fastBoundary[0]) {
|
||||
// This could be the start of the real end, mark it so and don't emit/skip it
|
||||
boundaryPtr = 1;
|
||||
} else {
|
||||
// Not the 1st char of our pattern, so emit and ignore
|
||||
_uploadWriteByte(in);
|
||||
boundaryPtr = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Found the boundary string, finish processing this file upload
|
||||
if (_currentHandler && _currentHandler->canUpload(*this, _currentUri)) {
|
||||
_currentHandler->upload(*this, _currentUri, *_currentUpload);
|
||||
}
|
||||
_currentUpload->totalSize += _currentUpload->currentSize;
|
||||
_currentUpload->status = UPLOAD_FILE_END;
|
||||
if (_currentHandler && _currentHandler->canUpload(*this, _currentUri)) {
|
||||
_currentHandler->upload(*this, _currentUri, *_currentUpload);
|
||||
}
|
||||
log_v("End File: %s Type: %s Size: %d", _currentUpload->filename.c_str(), _currentUpload->type.c_str(), (int)_currentUpload->totalSize);
|
||||
if (!client.connected()) {
|
||||
return _parseFormUploadAborted();
|
||||
}
|
||||
line = client.readStringUntil('\r');
|
||||
client.readStringUntil('\n');
|
||||
if (line == "--") { // extra two dashes mean we reached the end of all form fields
|
||||
log_v("Done Parsing POST");
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int iarg;
|
||||
int totalArgs = ((WEBSERVER_MAX_POST_ARGS - _postArgsLen) < _currentArgCount) ? (WEBSERVER_MAX_POST_ARGS - _postArgsLen) : _currentArgCount;
|
||||
for (iarg = 0; iarg < totalArgs; iarg++) {
|
||||
RequestArgument &arg = _postArgs[_postArgsLen++];
|
||||
arg.key = _currentArgs[iarg].key;
|
||||
arg.value = _currentArgs[iarg].value;
|
||||
}
|
||||
if (_currentArgs) {
|
||||
delete[] _currentArgs;
|
||||
}
|
||||
_currentArgs = new RequestArgument[_postArgsLen];
|
||||
for (iarg = 0; iarg < _postArgsLen; iarg++) {
|
||||
RequestArgument &arg = _currentArgs[iarg];
|
||||
arg.key = _postArgs[iarg].key;
|
||||
arg.value = _postArgs[iarg].value;
|
||||
}
|
||||
_currentArgCount = iarg;
|
||||
if (_postArgs) {
|
||||
delete[] _postArgs;
|
||||
_postArgs = nullptr;
|
||||
_postArgsLen = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
log_e("Error: line: %s", line.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
String WebServer::urlDecode(const String &text) {
|
||||
String decoded = "";
|
||||
char temp[] = "0x00";
|
||||
unsigned int len = text.length();
|
||||
unsigned int i = 0;
|
||||
while (i < len) {
|
||||
char decodedChar;
|
||||
char encodedChar = text.charAt(i++);
|
||||
if ((encodedChar == '%') && (i + 1 < len)) {
|
||||
temp[2] = text.charAt(i++);
|
||||
temp[3] = text.charAt(i++);
|
||||
|
||||
decodedChar = strtol(temp, NULL, 16);
|
||||
} else {
|
||||
if (encodedChar == '+') {
|
||||
decodedChar = ' ';
|
||||
} else {
|
||||
decodedChar = encodedChar; // normal ascii char
|
||||
}
|
||||
}
|
||||
decoded += decodedChar;
|
||||
}
|
||||
return decoded;
|
||||
}
|
||||
|
||||
bool WebServer::_parseFormUploadAborted() {
|
||||
_currentUpload->status = UPLOAD_FILE_ABORTED;
|
||||
if (_currentHandler && _currentHandler->canUpload(*this, _currentUri)) {
|
||||
_currentHandler->upload(*this, _currentUri, *_currentUpload);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
#ifndef URI_H
|
||||
#define URI_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <vector>
|
||||
|
||||
class Uri {
|
||||
|
||||
protected:
|
||||
const String _uri;
|
||||
|
||||
public:
|
||||
Uri(const char *uri) : _uri(uri) {}
|
||||
Uri(const String &uri) : _uri(uri) {}
|
||||
Uri(const __FlashStringHelper *uri) : _uri((const char *)uri) {}
|
||||
virtual ~Uri() {}
|
||||
|
||||
virtual Uri *clone() const {
|
||||
return new Uri(_uri);
|
||||
};
|
||||
|
||||
virtual void initPathArgs(__attribute__((unused)) std::vector<String> &pathArgs) {}
|
||||
|
||||
virtual bool canHandle(const String &requestUri, __attribute__((unused)) std::vector<String> &pathArgs) {
|
||||
return _uri == requestUri;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,331 @@
|
||||
/*
|
||||
WebServer.h - Dead simple web-server.
|
||||
Supports only one simultaneous client, knows how to handle GET and POST.
|
||||
|
||||
Copyright (c) 2014 Ivan Grokhotkov. All rights reserved.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling)
|
||||
*/
|
||||
|
||||
#ifndef WEBSERVER_H
|
||||
#define WEBSERVER_H
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include "FS.h"
|
||||
#include "Network.h"
|
||||
#include "HTTP_Method.h"
|
||||
#include "Uri.h"
|
||||
|
||||
enum HTTPUploadStatus {
|
||||
UPLOAD_FILE_START,
|
||||
UPLOAD_FILE_WRITE,
|
||||
UPLOAD_FILE_END,
|
||||
UPLOAD_FILE_ABORTED
|
||||
};
|
||||
enum HTTPRawStatus {
|
||||
RAW_START,
|
||||
RAW_WRITE,
|
||||
RAW_END,
|
||||
RAW_ABORTED
|
||||
};
|
||||
enum HTTPClientStatus {
|
||||
HC_NONE,
|
||||
HC_WAIT_READ,
|
||||
HC_WAIT_CLOSE
|
||||
};
|
||||
enum HTTPAuthMethod {
|
||||
BASIC_AUTH,
|
||||
DIGEST_AUTH,
|
||||
OTHER_AUTH
|
||||
};
|
||||
|
||||
#define HTTP_DOWNLOAD_UNIT_SIZE 1436
|
||||
|
||||
#ifndef HTTP_UPLOAD_BUFLEN
|
||||
#define HTTP_UPLOAD_BUFLEN 1436
|
||||
#endif
|
||||
|
||||
#ifndef HTTP_RAW_BUFLEN
|
||||
#define HTTP_RAW_BUFLEN 1436
|
||||
#endif
|
||||
|
||||
#define HTTP_MAX_DATA_WAIT 5000 //ms to wait for the client to send the request
|
||||
#define HTTP_MAX_POST_WAIT 5000 //ms to wait for POST data to arrive
|
||||
#define HTTP_MAX_SEND_WAIT 5000 //ms to wait for data chunk to be ACKed
|
||||
#define HTTP_MAX_CLOSE_WAIT 5000 //ms to wait for the client to close the connection
|
||||
#define HTTP_MAX_BASIC_AUTH_LEN 256 // maximum length of a basic Auth base64 encoded username:password string
|
||||
|
||||
#define CONTENT_LENGTH_UNKNOWN ((size_t) - 1)
|
||||
#define CONTENT_LENGTH_NOT_SET ((size_t) - 2)
|
||||
|
||||
class WebServer;
|
||||
|
||||
typedef struct {
|
||||
HTTPUploadStatus status;
|
||||
String filename;
|
||||
String name;
|
||||
String type;
|
||||
size_t totalSize; // file size
|
||||
size_t currentSize; // size of data currently in buf
|
||||
uint8_t buf[HTTP_UPLOAD_BUFLEN];
|
||||
} HTTPUpload;
|
||||
|
||||
typedef struct {
|
||||
HTTPRawStatus status;
|
||||
size_t totalSize; // content size
|
||||
size_t currentSize; // size of data currently in buf
|
||||
uint8_t buf[HTTP_RAW_BUFLEN];
|
||||
void *data; // additional data
|
||||
} HTTPRaw;
|
||||
|
||||
#include "middleware/Middleware.h"
|
||||
#include "detail/RequestHandler.h"
|
||||
|
||||
namespace fs {
|
||||
class FS;
|
||||
}
|
||||
|
||||
class WebServer {
|
||||
public:
|
||||
WebServer(IPAddress addr, int port = 80);
|
||||
WebServer(int port = 80);
|
||||
virtual ~WebServer();
|
||||
|
||||
virtual void begin();
|
||||
virtual void begin(uint16_t port);
|
||||
virtual void handleClient();
|
||||
|
||||
virtual void close();
|
||||
void stop();
|
||||
|
||||
const String AuthTypeDigest = F("Digest");
|
||||
const String AuthTypeBasic = F("Basic");
|
||||
|
||||
void chunkResponseBegin(const char *contentType = "text/plain");
|
||||
void chunkWrite(const char *data, size_t length);
|
||||
void chunkResponseEnd();
|
||||
|
||||
/* Callbackhandler for authentication. The extra parameters depend on the
|
||||
* HTTPAuthMethod mode:
|
||||
*
|
||||
* BASIC_AUTH enteredUsernameOrReq contains the username entered by the user
|
||||
* param[0] password entered (in the clear)
|
||||
* param[1] authentication realm.
|
||||
*
|
||||
* To return - the password the user entered password is compared to. Or Null on fail.
|
||||
*
|
||||
* DIGEST_AUTH enteredUsernameOrReq contains the username entered by the user
|
||||
* param[0] autenticaiton realm
|
||||
* param[1] authentication URI
|
||||
*
|
||||
* To return - the password of which the digest will be based on for comparison. Or NULL
|
||||
* to fail.
|
||||
*
|
||||
* OTHER_AUTH enteredUsernameOrReq rest of the auth line.
|
||||
* params empty array
|
||||
*
|
||||
* To return - NULL to fail; or any string.
|
||||
*/
|
||||
typedef std::function<String *(HTTPAuthMethod mode, String enteredUsernameOrReq, String extraParams[])> THandlerFunctionAuthCheck;
|
||||
|
||||
bool authenticate(THandlerFunctionAuthCheck fn);
|
||||
bool authenticate(const char *username, const char *password);
|
||||
bool authenticateBasicSHA1(const char *_username, const char *_sha1AsBase64orHex);
|
||||
|
||||
void requestAuthentication(HTTPAuthMethod mode = BASIC_AUTH, const char *realm = NULL, const String &authFailMsg = String(""));
|
||||
|
||||
typedef std::function<void(void)> THandlerFunction;
|
||||
typedef std::function<bool(WebServer &server)> FilterFunction;
|
||||
RequestHandler &on(const Uri &uri, THandlerFunction fn);
|
||||
RequestHandler &on(const Uri &uri, HTTPMethod method, THandlerFunction fn);
|
||||
RequestHandler &on(const Uri &uri, HTTPMethod method, THandlerFunction fn, THandlerFunction ufn); //ufn handles file uploads
|
||||
bool removeRoute(const char *uri);
|
||||
bool removeRoute(const char *uri, HTTPMethod method);
|
||||
bool removeRoute(const String &uri);
|
||||
bool removeRoute(const String &uri, HTTPMethod method);
|
||||
void addHandler(RequestHandler *handler);
|
||||
bool removeHandler(RequestHandler *handler);
|
||||
void serveStatic(const char *uri, fs::FS &fs, const char *path, const char *cache_header = NULL);
|
||||
void onNotFound(THandlerFunction fn); //called when handler is not assigned
|
||||
void onFileUpload(THandlerFunction ufn); //handle file uploads
|
||||
|
||||
WebServer &addMiddleware(Middleware *middleware);
|
||||
WebServer &addMiddleware(Middleware::Function fn);
|
||||
WebServer &removeMiddleware(Middleware *middleware);
|
||||
|
||||
String uri() const {
|
||||
return _currentUri;
|
||||
}
|
||||
HTTPMethod method() const {
|
||||
return _currentMethod;
|
||||
}
|
||||
virtual NetworkClient &client() {
|
||||
return _currentClient;
|
||||
}
|
||||
HTTPUpload &upload() {
|
||||
return *_currentUpload;
|
||||
}
|
||||
HTTPRaw &raw() {
|
||||
return *_currentRaw;
|
||||
}
|
||||
|
||||
String pathArg(unsigned int i) const; // get request path argument by number
|
||||
String arg(const String &name) const; // get request argument value by name
|
||||
String arg(int i) const; // get request argument value by number
|
||||
String argName(int i) const; // get request argument name by number
|
||||
int args() const; // get arguments count
|
||||
bool hasArg(const String &name) const; // check if argument exists
|
||||
void collectHeaders(const char *headerKeys[], const size_t headerKeysCount); // set the request headers to collect
|
||||
void collectAllHeaders(); // collect all request headers
|
||||
String header(const String &name) const; // get request header value by name
|
||||
String header(int i) const; // get request header value by number
|
||||
String headerName(int i) const; // get request header name by number
|
||||
int headers() const; // get header count
|
||||
bool hasHeader(const String &name) const; // check if header exists
|
||||
|
||||
int clientContentLength() const; // return "content-length" of incoming HTTP header from "_currentClient"
|
||||
const String version() const; // get the HTTP version string
|
||||
String hostHeader() const; // get request host header if available or empty String if not
|
||||
|
||||
int responseCode() const; // get the HTTP response code set
|
||||
int responseHeaders() const; // get the HTTP response headers count
|
||||
const String &responseHeader(String name) const; // get the HTTP response header value by name
|
||||
const String &responseHeader(int i) const; // get the HTTP response header value by number
|
||||
const String &responseHeaderName(int i) const; // get the HTTP response header name by number
|
||||
bool hasResponseHeader(const String &name) const; // check if response header exists
|
||||
|
||||
// send response to the client
|
||||
// code - HTTP response code, can be 200 or 404
|
||||
// content_type - HTTP content type, like "text/plain" or "image/png"
|
||||
// content - actual content body
|
||||
void send(int code, const char *content_type = NULL, const String &content = String(""));
|
||||
void send(int code, char *content_type, const String &content);
|
||||
void send(int code, const String &content_type, const String &content);
|
||||
void send(int code, const char *content_type, const char *content);
|
||||
|
||||
void send_P(int code, PGM_P content_type, PGM_P content);
|
||||
void send_P(int code, PGM_P content_type, PGM_P content, size_t contentLength);
|
||||
|
||||
void enableDelay(boolean value);
|
||||
void enableCORS(boolean value = true);
|
||||
void enableCrossOrigin(boolean value = true);
|
||||
typedef std::function<String(FS &fs, const String &fName)> ETagFunction;
|
||||
void enableETag(bool enable, ETagFunction fn = nullptr);
|
||||
|
||||
void setContentLength(const size_t contentLength);
|
||||
void sendHeader(const String &name, const String &value, bool first = false);
|
||||
void sendContent(const String &content);
|
||||
void sendContent(const char *content, size_t contentLength);
|
||||
void sendContent_P(PGM_P content);
|
||||
void sendContent_P(PGM_P content, size_t size);
|
||||
|
||||
static String urlDecode(const String &text);
|
||||
|
||||
template<typename T> size_t streamFile(T &file, const String &contentType, const int code = 200) {
|
||||
_streamFileCore(file.size(), file.name(), contentType, code);
|
||||
return _currentClient.write(file);
|
||||
}
|
||||
|
||||
bool _eTagEnabled = false;
|
||||
ETagFunction _eTagFunction = nullptr;
|
||||
|
||||
static String responseCodeToString(int code);
|
||||
|
||||
private:
|
||||
bool _chunkedResponseActive = false;
|
||||
NetworkClient _chunkedClient; // Store by value, no dangling pointer
|
||||
|
||||
protected:
|
||||
virtual size_t _currentClientWrite(const char *b, size_t l) {
|
||||
return _currentClient.write(b, l);
|
||||
}
|
||||
virtual size_t _currentClientWrite_P(PGM_P b, size_t l) {
|
||||
return _currentClient.write_P(b, l);
|
||||
}
|
||||
void _addRequestHandler(RequestHandler *handler);
|
||||
bool _removeRequestHandler(RequestHandler *handler);
|
||||
bool _handleRequest();
|
||||
void _finalizeResponse();
|
||||
bool _parseRequest(NetworkClient &client);
|
||||
void _parseArguments(const String &data);
|
||||
bool _parseForm(NetworkClient &client, const String &boundary, uint32_t len);
|
||||
bool _parseFormUploadAborted();
|
||||
void _uploadWriteByte(uint8_t b);
|
||||
int _uploadReadByte(NetworkClient &client);
|
||||
void _prepareHeader(String &response, int code, const char *content_type, size_t contentLength);
|
||||
bool _collectHeader(const char *headerName, const char *headerValue);
|
||||
|
||||
void _streamFileCore(const size_t fileSize, const String &fileName, const String &contentType, const int code = 200);
|
||||
|
||||
String _getRandomHexString();
|
||||
// for extracting Auth parameters
|
||||
String _extractParam(String &authReq, const String ¶m, const char delimit = '"');
|
||||
|
||||
void _clearResponseHeaders();
|
||||
void _clearRequestHeaders();
|
||||
|
||||
struct RequestArgument {
|
||||
String key;
|
||||
String value;
|
||||
RequestArgument *next;
|
||||
};
|
||||
|
||||
boolean _corsEnabled = false;
|
||||
NetworkServer _server;
|
||||
|
||||
NetworkClient _currentClient;
|
||||
HTTPMethod _currentMethod = HTTP_ANY;
|
||||
String _currentUri;
|
||||
uint8_t _currentVersion = 0;
|
||||
HTTPClientStatus _currentStatus = HC_NONE;
|
||||
unsigned long _statusChange = 0;
|
||||
boolean _nullDelay = true;
|
||||
|
||||
RequestHandler *_currentHandler = nullptr;
|
||||
RequestHandler *_firstHandler = nullptr;
|
||||
RequestHandler *_lastHandler = nullptr;
|
||||
THandlerFunction _notFoundHandler = nullptr;
|
||||
THandlerFunction _fileUploadHandler = nullptr;
|
||||
|
||||
int _currentArgCount = 0;
|
||||
RequestArgument *_currentArgs = nullptr;
|
||||
int _postArgsLen = 0;
|
||||
RequestArgument *_postArgs = nullptr;
|
||||
|
||||
std::unique_ptr<HTTPUpload> _currentUpload;
|
||||
std::unique_ptr<HTTPRaw> _currentRaw;
|
||||
|
||||
int _headerKeysCount = 0;
|
||||
RequestArgument *_currentHeaders = nullptr;
|
||||
size_t _contentLength = 0;
|
||||
int _clientContentLength = 0; // "Content-Length" from header of incoming POST or GET request
|
||||
RequestArgument *_responseHeaders = nullptr;
|
||||
|
||||
String _hostHeader;
|
||||
bool _chunked = false;
|
||||
|
||||
String _snonce; // Store noance and opaque for future comparison
|
||||
String _sopaque;
|
||||
String _srealm; // Store the Auth realm between Calls
|
||||
|
||||
int _responseHeaderCount = 0;
|
||||
int _responseCode = 0;
|
||||
bool _collectAllHeaders = false;
|
||||
MiddlewareChain *_chain = nullptr;
|
||||
};
|
||||
|
||||
#endif //ESP8266WEBSERVER_H
|
||||
@@ -0,0 +1,99 @@
|
||||
#ifndef REQUESTHANDLER_H
|
||||
#define REQUESTHANDLER_H
|
||||
|
||||
#include <vector>
|
||||
#include <assert.h>
|
||||
|
||||
class RequestHandler {
|
||||
public:
|
||||
virtual ~RequestHandler() {
|
||||
delete _chain;
|
||||
}
|
||||
|
||||
/*
|
||||
note: old handler API for backward compatibility
|
||||
*/
|
||||
|
||||
virtual bool canHandle(HTTPMethod method, const String &uri) {
|
||||
(void)method;
|
||||
(void)uri;
|
||||
return false;
|
||||
}
|
||||
virtual bool canUpload(const String &uri) {
|
||||
(void)uri;
|
||||
return false;
|
||||
}
|
||||
virtual bool canRaw(const String &uri) {
|
||||
(void)uri;
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
note: new handler API with support for filters etc.
|
||||
*/
|
||||
|
||||
virtual bool canHandle(WebServer &server, HTTPMethod method, const String &uri) {
|
||||
(void)server;
|
||||
(void)method;
|
||||
(void)uri;
|
||||
return false;
|
||||
}
|
||||
virtual bool canUpload(WebServer &server, const String &uri) {
|
||||
(void)server;
|
||||
(void)uri;
|
||||
return false;
|
||||
}
|
||||
virtual bool canRaw(WebServer &server, const String &uri) {
|
||||
(void)server;
|
||||
(void)uri;
|
||||
return false;
|
||||
}
|
||||
virtual bool handle(WebServer &server, HTTPMethod requestMethod, const String &requestUri) {
|
||||
(void)server;
|
||||
(void)requestMethod;
|
||||
(void)requestUri;
|
||||
return false;
|
||||
}
|
||||
virtual void upload(WebServer &server, const String &requestUri, HTTPUpload &upload) {
|
||||
(void)server;
|
||||
(void)requestUri;
|
||||
(void)upload;
|
||||
}
|
||||
virtual void raw(WebServer &server, const String &requestUri, HTTPRaw &raw) {
|
||||
(void)server;
|
||||
(void)requestUri;
|
||||
(void)raw;
|
||||
}
|
||||
|
||||
virtual RequestHandler &setFilter(std::function<bool(WebServer &)> filter) {
|
||||
(void)filter;
|
||||
return *this;
|
||||
}
|
||||
|
||||
RequestHandler *next() {
|
||||
return _next;
|
||||
}
|
||||
void next(RequestHandler *r) {
|
||||
_next = r;
|
||||
}
|
||||
|
||||
RequestHandler &addMiddleware(Middleware *middleware);
|
||||
RequestHandler &addMiddleware(Middleware::Function fn);
|
||||
RequestHandler &removeMiddleware(Middleware *middleware);
|
||||
bool process(WebServer &server, HTTPMethod requestMethod, String requestUri);
|
||||
|
||||
private:
|
||||
RequestHandler *_next = nullptr;
|
||||
MiddlewareChain *_chain = nullptr;
|
||||
|
||||
protected:
|
||||
std::vector<String> pathArgs;
|
||||
|
||||
public:
|
||||
const String &pathArg(unsigned int i) {
|
||||
assert(i < pathArgs.size());
|
||||
return pathArgs[i];
|
||||
}
|
||||
};
|
||||
|
||||
#endif //REQUESTHANDLER_H
|
||||
@@ -0,0 +1,300 @@
|
||||
#ifndef REQUESTHANDLERSIMPL_H
|
||||
#define REQUESTHANDLERSIMPL_H
|
||||
|
||||
#include "RequestHandler.h"
|
||||
#include "mimetable.h"
|
||||
#include "WString.h"
|
||||
#include "Uri.h"
|
||||
#include <MD5Builder.h>
|
||||
#include <base64.h>
|
||||
|
||||
using namespace mime;
|
||||
|
||||
RequestHandler &RequestHandler::addMiddleware(Middleware *middleware) {
|
||||
if (!_chain) {
|
||||
_chain = new MiddlewareChain();
|
||||
}
|
||||
_chain->addMiddleware(middleware);
|
||||
return *this;
|
||||
}
|
||||
|
||||
RequestHandler &RequestHandler::addMiddleware(Middleware::Function fn) {
|
||||
if (!_chain) {
|
||||
_chain = new MiddlewareChain();
|
||||
}
|
||||
_chain->addMiddleware(fn);
|
||||
return *this;
|
||||
}
|
||||
|
||||
RequestHandler &RequestHandler::removeMiddleware(Middleware *middleware) {
|
||||
if (_chain) {
|
||||
_chain->removeMiddleware(middleware);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool RequestHandler::process(WebServer &server, HTTPMethod requestMethod, String requestUri) {
|
||||
if (_chain) {
|
||||
return _chain->runChain(server, [this, &server, &requestMethod, &requestUri]() {
|
||||
(void)requestUri;
|
||||
return handle(server, requestMethod, requestUri);
|
||||
});
|
||||
} else {
|
||||
return handle(server, requestMethod, requestUri);
|
||||
}
|
||||
}
|
||||
|
||||
class FunctionRequestHandler : public RequestHandler {
|
||||
public:
|
||||
FunctionRequestHandler(WebServer::THandlerFunction fn, WebServer::THandlerFunction ufn, const Uri &uri, HTTPMethod method)
|
||||
: _fn(fn), _ufn(ufn), _uri(uri.clone()), _method(method) {
|
||||
_uri->initPathArgs(pathArgs);
|
||||
}
|
||||
|
||||
~FunctionRequestHandler() {
|
||||
delete _uri;
|
||||
}
|
||||
|
||||
bool canHandle(HTTPMethod requestMethod, const String &requestUri) override {
|
||||
if (_method != HTTP_ANY && _method != requestMethod) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return _uri->canHandle(requestUri, pathArgs);
|
||||
}
|
||||
|
||||
bool canUpload(const String &requestUri) override {
|
||||
if (!_ufn || !canHandle(HTTP_POST, requestUri)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool canRaw(const String &requestUri) override {
|
||||
(void)requestUri;
|
||||
if (!_ufn || _method == HTTP_GET) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool canHandle(WebServer &server, HTTPMethod requestMethod, const String &requestUri) override {
|
||||
if (_method != HTTP_ANY && _method != requestMethod) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return _uri->canHandle(requestUri, pathArgs) && (_filter != NULL ? _filter(server) : true);
|
||||
}
|
||||
|
||||
bool canUpload(WebServer &server, const String &requestUri) override {
|
||||
if (!_ufn || !canHandle(server, HTTP_POST, requestUri)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool canRaw(WebServer &server, const String &requestUri) override {
|
||||
(void)requestUri;
|
||||
if (!_ufn || _method == HTTP_GET || (_filter != NULL ? _filter(server) == false : false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool handle(WebServer &server, HTTPMethod requestMethod, const String &requestUri) override {
|
||||
if (!canHandle(server, requestMethod, requestUri)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
_fn();
|
||||
return true;
|
||||
}
|
||||
|
||||
void upload(WebServer &server, const String &requestUri, HTTPUpload &upload) override {
|
||||
(void)upload;
|
||||
if (canUpload(server, requestUri)) {
|
||||
_ufn();
|
||||
}
|
||||
}
|
||||
|
||||
void raw(WebServer &server, const String &requestUri, HTTPRaw &raw) override {
|
||||
(void)raw;
|
||||
if (canRaw(server, requestUri)) {
|
||||
_ufn();
|
||||
}
|
||||
}
|
||||
|
||||
FunctionRequestHandler &setFilter(WebServer::FilterFunction filter) {
|
||||
_filter = filter;
|
||||
return *this;
|
||||
}
|
||||
|
||||
protected:
|
||||
WebServer::THandlerFunction _fn;
|
||||
WebServer::THandlerFunction _ufn;
|
||||
// _filter should return 'true' when the request should be handled
|
||||
// and 'false' when the request should be ignored
|
||||
WebServer::FilterFunction _filter;
|
||||
Uri *_uri;
|
||||
HTTPMethod _method;
|
||||
};
|
||||
|
||||
class StaticRequestHandler : public RequestHandler {
|
||||
public:
|
||||
StaticRequestHandler(FS &fs, const char *path, const char *uri, const char *cache_header) : _fs(fs), _uri(uri), _path(path), _cache_header(cache_header) {
|
||||
File f = fs.open(path);
|
||||
_isFile = (f && (!f.isDirectory()));
|
||||
log_v(
|
||||
"StaticRequestHandler: path=%s uri=%s isFile=%d, cache_header=%s\r\n", path, uri, _isFile, cache_header ? cache_header : ""
|
||||
); // issue 5506 - cache_header can be nullptr
|
||||
_baseUriLength = _uri.length();
|
||||
}
|
||||
|
||||
bool canHandle(HTTPMethod requestMethod, const String &requestUri) override {
|
||||
if (requestMethod != HTTP_GET) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((_isFile && requestUri != _uri) || !requestUri.startsWith(_uri)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool canHandle(WebServer &server, HTTPMethod requestMethod, const String &requestUri) override {
|
||||
if (requestMethod != HTTP_GET) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((_isFile && requestUri != _uri) || !requestUri.startsWith(_uri)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_filter != NULL ? _filter(server) == false : false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool handle(WebServer &server, HTTPMethod requestMethod, const String &requestUri) override {
|
||||
if (!canHandle(server, requestMethod, requestUri)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
log_v("StaticRequestHandler::handle: request=%s _uri=%s\r\n", requestUri.c_str(), _uri.c_str());
|
||||
|
||||
String path(_path);
|
||||
|
||||
if (!_isFile) {
|
||||
// Base URI doesn't point to a file.
|
||||
// If a directory is requested, look for index file.
|
||||
if (requestUri.endsWith("/")) {
|
||||
return handle(server, requestMethod, String(requestUri + "index.htm"));
|
||||
}
|
||||
|
||||
// Append whatever follows this URI in request to get the file path.
|
||||
path += requestUri.substring(_baseUriLength);
|
||||
}
|
||||
log_v("StaticRequestHandler::handle: path=%s, isFile=%d\r\n", path.c_str(), _isFile);
|
||||
|
||||
String contentType = getContentType(path);
|
||||
|
||||
// look for gz file, only if the original specified path is not a gz. So part only works to send gzip via content encoding when a non compressed is asked for
|
||||
// if you point the the path to gzip you will serve the gzip as content type "application/x-gzip", not text or javascript etc...
|
||||
if (!path.endsWith(FPSTR(mimeTable[gz].endsWith)) && !_fs.exists(path)) {
|
||||
String pathWithGz = path + FPSTR(mimeTable[gz].endsWith);
|
||||
if (_fs.exists(pathWithGz)) {
|
||||
path += FPSTR(mimeTable[gz].endsWith);
|
||||
}
|
||||
}
|
||||
|
||||
File f = _fs.open(path, "r");
|
||||
if (!f || !f.available()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String eTagCode;
|
||||
|
||||
if (server._eTagEnabled) {
|
||||
if (server._eTagFunction) {
|
||||
eTagCode = (server._eTagFunction)(_fs, path);
|
||||
} else {
|
||||
eTagCode = calcETag(_fs, path);
|
||||
}
|
||||
|
||||
if (server.header("If-None-Match") == eTagCode) {
|
||||
server.send(304);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (_cache_header.length() != 0) {
|
||||
server.sendHeader("Cache-Control", _cache_header);
|
||||
}
|
||||
|
||||
if ((server._eTagEnabled) && (eTagCode.length() > 0)) {
|
||||
server.sendHeader("ETag", eTagCode);
|
||||
}
|
||||
|
||||
server.streamFile(f, contentType);
|
||||
return true;
|
||||
}
|
||||
|
||||
static String getContentType(const String &path) {
|
||||
char buff[sizeof(mimeTable[0].mimeType)];
|
||||
// Check all entries but last one for match, return if found
|
||||
for (size_t i = 0; i < sizeof(mimeTable) / sizeof(mimeTable[0]) - 1; i++) {
|
||||
strcpy_P(buff, mimeTable[i].endsWith);
|
||||
if (path.endsWith(buff)) {
|
||||
strcpy_P(buff, mimeTable[i].mimeType);
|
||||
return String(buff);
|
||||
}
|
||||
}
|
||||
// Fall-through and just return default type
|
||||
strcpy_P(buff, mimeTable[sizeof(mimeTable) / sizeof(mimeTable[0]) - 1].mimeType);
|
||||
return String(buff);
|
||||
}
|
||||
|
||||
// calculate an ETag for a file in filesystem based on md5 checksum
|
||||
// that can be used in the http headers - include quotes.
|
||||
static String calcETag(FS &fs, const String &path) {
|
||||
String result;
|
||||
|
||||
// calculate eTag using md5 checksum
|
||||
uint8_t md5_buf[16];
|
||||
File f = fs.open(path, "r");
|
||||
MD5Builder calcMD5;
|
||||
calcMD5.begin();
|
||||
calcMD5.addStream(f, f.size());
|
||||
calcMD5.calculate();
|
||||
calcMD5.getBytes(md5_buf);
|
||||
f.close();
|
||||
// create a minimal-length eTag using base64 byte[]->text encoding.
|
||||
result = "\"" + base64::encode(md5_buf, 16) + "\"";
|
||||
return (result);
|
||||
} // calcETag
|
||||
|
||||
StaticRequestHandler &setFilter(WebServer::FilterFunction filter) {
|
||||
_filter = filter;
|
||||
return *this;
|
||||
}
|
||||
|
||||
protected:
|
||||
// _filter should return 'true' when the request should be handled
|
||||
// and 'false' when the request should be ignored
|
||||
WebServer::FilterFunction _filter;
|
||||
FS _fs;
|
||||
String _uri;
|
||||
String _path;
|
||||
String _cache_header;
|
||||
bool _isFile;
|
||||
size_t _baseUriLength;
|
||||
};
|
||||
|
||||
#endif //REQUESTHANDLERSIMPL_H
|
||||
@@ -0,0 +1,34 @@
|
||||
#include "mimetable.h"
|
||||
#include "pgmspace.h"
|
||||
|
||||
namespace mime {
|
||||
|
||||
// Table of extension->MIME strings stored in PROGMEM, needs to be global due to GCC section typing rules
|
||||
const Entry mimeTable[maxType] = {
|
||||
{".html", "text/html"},
|
||||
{".htm", "text/html"},
|
||||
{".css", "text/css"},
|
||||
{".txt", "text/plain"},
|
||||
{".js", "application/javascript"},
|
||||
{".mjs", "text/javascript"},
|
||||
{".json", "application/json"},
|
||||
{".png", "image/png"},
|
||||
{".gif", "image/gif"},
|
||||
{".jpg", "image/jpeg"},
|
||||
{".ico", "image/x-icon"},
|
||||
{".svg", "image/svg+xml"},
|
||||
{".ttf", "application/x-font-ttf"},
|
||||
{".otf", "application/x-font-opentype"},
|
||||
{".woff", "application/font-woff"},
|
||||
{".woff2", "application/font-woff2"},
|
||||
{".eot", "application/vnd.ms-fontobject"},
|
||||
{".sfnt", "application/font-sfnt"},
|
||||
{".xml", "text/xml"},
|
||||
{".pdf", "application/pdf"},
|
||||
{".zip", "application/zip"},
|
||||
{".gz", "application/x-gzip"},
|
||||
{".appcache", "text/cache-manifest"},
|
||||
{"", "application/octet-stream"}
|
||||
};
|
||||
|
||||
} // namespace mime
|
||||
@@ -0,0 +1,42 @@
|
||||
#ifndef __MIMETABLE_H__
|
||||
#define __MIMETABLE_H__
|
||||
|
||||
namespace mime {
|
||||
|
||||
enum type {
|
||||
html,
|
||||
htm,
|
||||
css,
|
||||
txt,
|
||||
js,
|
||||
mjs,
|
||||
json,
|
||||
png,
|
||||
gif,
|
||||
jpg,
|
||||
ico,
|
||||
svg,
|
||||
ttf,
|
||||
otf,
|
||||
woff,
|
||||
woff2,
|
||||
eot,
|
||||
sfnt,
|
||||
xml,
|
||||
pdf,
|
||||
zip,
|
||||
gz,
|
||||
appcache,
|
||||
none,
|
||||
maxType
|
||||
};
|
||||
|
||||
struct Entry {
|
||||
const char endsWith[16];
|
||||
const char mimeType[32];
|
||||
};
|
||||
|
||||
extern const Entry mimeTable[maxType];
|
||||
} // namespace mime
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,82 @@
|
||||
#include "Middlewares.h"
|
||||
|
||||
AuthenticationMiddleware &AuthenticationMiddleware::setUsername(const char *username) {
|
||||
_username = username;
|
||||
_callback = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
AuthenticationMiddleware &AuthenticationMiddleware::setPassword(const char *password) {
|
||||
_password = password;
|
||||
_hash = false;
|
||||
_callback = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
AuthenticationMiddleware &AuthenticationMiddleware::setPasswordHash(const char *sha1AsBase64orHex) {
|
||||
_password = sha1AsBase64orHex;
|
||||
_hash = true;
|
||||
_callback = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
AuthenticationMiddleware &AuthenticationMiddleware::setCallback(WebServer::THandlerFunctionAuthCheck fn) {
|
||||
assert(fn);
|
||||
_callback = fn;
|
||||
_hash = false;
|
||||
_username = emptyString;
|
||||
_password = emptyString;
|
||||
return *this;
|
||||
}
|
||||
|
||||
AuthenticationMiddleware &AuthenticationMiddleware::setRealm(const char *realm) {
|
||||
_realm = realm;
|
||||
return *this;
|
||||
}
|
||||
|
||||
AuthenticationMiddleware &AuthenticationMiddleware::setAuthMethod(HTTPAuthMethod method) {
|
||||
_method = method;
|
||||
return *this;
|
||||
}
|
||||
|
||||
AuthenticationMiddleware &AuthenticationMiddleware::setAuthFailureMessage(const char *message) {
|
||||
_authFailMsg = message;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool AuthenticationMiddleware::isAllowed(WebServer &server) const {
|
||||
if (_callback) {
|
||||
return server.authenticate(_callback);
|
||||
}
|
||||
|
||||
if (!_username.isEmpty() && !_password.isEmpty()) {
|
||||
if (_hash) {
|
||||
return server.authenticateBasicSHA1(_username.c_str(), _password.c_str());
|
||||
} else {
|
||||
return server.authenticate(_username.c_str(), _password.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AuthenticationMiddleware::run(WebServer &server, Middleware::Callback next) {
|
||||
bool authenticationRequired = false;
|
||||
|
||||
if (_callback) {
|
||||
authenticationRequired = !server.authenticate(_callback);
|
||||
} else if (!_username.isEmpty() && !_password.isEmpty()) {
|
||||
if (_hash) {
|
||||
authenticationRequired = !server.authenticateBasicSHA1(_username.c_str(), _password.c_str());
|
||||
} else {
|
||||
authenticationRequired = !server.authenticate(_username.c_str(), _password.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
if (authenticationRequired) {
|
||||
server.requestAuthentication(_method, _realm, _authFailMsg);
|
||||
return true;
|
||||
} else {
|
||||
return next();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
#include "Middlewares.h"
|
||||
|
||||
CorsMiddleware &CorsMiddleware::setOrigin(const char *origin) {
|
||||
_origin = origin;
|
||||
return *this;
|
||||
}
|
||||
|
||||
CorsMiddleware &CorsMiddleware::setMethods(const char *methods) {
|
||||
_methods = methods;
|
||||
return *this;
|
||||
}
|
||||
|
||||
CorsMiddleware &CorsMiddleware::setHeaders(const char *headers) {
|
||||
_headers = headers;
|
||||
return *this;
|
||||
}
|
||||
|
||||
CorsMiddleware &CorsMiddleware::setAllowCredentials(bool credentials) {
|
||||
_credentials = credentials;
|
||||
return *this;
|
||||
}
|
||||
|
||||
CorsMiddleware &CorsMiddleware::setMaxAge(uint32_t seconds) {
|
||||
_maxAge = seconds;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void CorsMiddleware::addCORSHeaders(WebServer &server) {
|
||||
server.sendHeader(F("Access-Control-Allow-Origin"), _origin.c_str());
|
||||
server.sendHeader(F("Access-Control-Allow-Methods"), _methods.c_str());
|
||||
server.sendHeader(F("Access-Control-Allow-Headers"), _headers.c_str());
|
||||
server.sendHeader(F("Access-Control-Allow-Credentials"), _credentials ? F("true") : F("false"));
|
||||
server.sendHeader(F("Access-Control-Max-Age"), String(_maxAge).c_str());
|
||||
}
|
||||
|
||||
bool CorsMiddleware::run(WebServer &server, Middleware::Callback next) {
|
||||
// Origin header ? => CORS handling
|
||||
if (server.hasHeader(F("Origin"))) {
|
||||
addCORSHeaders(server);
|
||||
// check if this is a preflight request => handle it and return
|
||||
if (server.method() == HTTP_OPTIONS) {
|
||||
server.send(200);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return next();
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
#include "Middlewares.h"
|
||||
|
||||
void LoggingMiddleware::setOutput(Print &output) {
|
||||
_out = &output;
|
||||
}
|
||||
|
||||
bool LoggingMiddleware::run(WebServer &server, Middleware::Callback next) {
|
||||
if (_out == nullptr) {
|
||||
return next();
|
||||
}
|
||||
|
||||
_out->print(F("* Connection from "));
|
||||
_out->print(server.client().remoteIP().toString());
|
||||
_out->print(F(":"));
|
||||
_out->println(server.client().remotePort());
|
||||
|
||||
_out->print(F("> "));
|
||||
const HTTPMethod method = server.method();
|
||||
if (method == HTTP_ANY) {
|
||||
_out->print(F("HTTP_ANY"));
|
||||
} else {
|
||||
_out->print(http_method_str(method));
|
||||
}
|
||||
_out->print(F(" "));
|
||||
_out->print(server.uri());
|
||||
_out->print(F(" "));
|
||||
_out->println(server.version());
|
||||
|
||||
int n = server.headers();
|
||||
for (int i = 0; i < n; i++) {
|
||||
String v = server.header(i);
|
||||
if (!v.isEmpty()) {
|
||||
// because these 2 are always there, eventually empty: "Authorization", "If-None-Match"
|
||||
_out->print(F("> "));
|
||||
_out->print(server.headerName(i));
|
||||
_out->print(F(": "));
|
||||
_out->println(server.header(i));
|
||||
}
|
||||
}
|
||||
|
||||
_out->println(F(">"));
|
||||
|
||||
uint32_t elapsed = millis();
|
||||
const bool ret = next();
|
||||
elapsed = millis() - elapsed;
|
||||
|
||||
if (ret) {
|
||||
_out->print(F("* Processed in "));
|
||||
_out->print(elapsed);
|
||||
_out->println(F(" ms"));
|
||||
_out->print(F("< "));
|
||||
_out->print(F("HTTP/1."));
|
||||
_out->print(server.version());
|
||||
_out->print(F(" "));
|
||||
_out->print(server.responseCode());
|
||||
_out->print(F(" "));
|
||||
_out->println(WebServer::responseCodeToString(server.responseCode()));
|
||||
|
||||
n = server.responseHeaders();
|
||||
for (int i = 0; i < n; i++) {
|
||||
_out->print(F("< "));
|
||||
_out->print(server.responseHeaderName(i));
|
||||
_out->print(F(": "));
|
||||
_out->println(server.responseHeader(i));
|
||||
}
|
||||
|
||||
_out->println(F("<"));
|
||||
|
||||
} else {
|
||||
_out->println(F("* Not processed!"));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
#ifndef MIDDLEWARE_H
|
||||
#define MIDDLEWARE_H
|
||||
|
||||
#include <assert.h>
|
||||
#include <functional>
|
||||
|
||||
class MiddlewareChain;
|
||||
class WebServer;
|
||||
|
||||
class Middleware {
|
||||
public:
|
||||
typedef std::function<bool(void)> Callback;
|
||||
typedef std::function<bool(WebServer &server, Callback next)> Function;
|
||||
|
||||
virtual ~Middleware() {}
|
||||
|
||||
virtual bool run(WebServer &server, Callback next) {
|
||||
(void)server;
|
||||
return next();
|
||||
};
|
||||
|
||||
private:
|
||||
friend MiddlewareChain;
|
||||
Middleware *_next = nullptr;
|
||||
bool _freeOnRemoval = false;
|
||||
};
|
||||
|
||||
class MiddlewareFunction : public Middleware {
|
||||
public:
|
||||
MiddlewareFunction(Middleware::Function fn) : _fn(fn) {}
|
||||
|
||||
bool run(WebServer &server, Middleware::Callback next) override {
|
||||
return _fn(server, next);
|
||||
}
|
||||
|
||||
private:
|
||||
Middleware::Function _fn;
|
||||
};
|
||||
|
||||
class MiddlewareChain {
|
||||
public:
|
||||
~MiddlewareChain();
|
||||
|
||||
void addMiddleware(Middleware::Function fn);
|
||||
void addMiddleware(Middleware *middleware);
|
||||
bool removeMiddleware(Middleware *middleware);
|
||||
|
||||
bool runChain(WebServer &server, Middleware::Callback finalizer);
|
||||
|
||||
private:
|
||||
Middleware *_root = nullptr;
|
||||
Middleware *_current = nullptr;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,73 @@
|
||||
#include "Middleware.h"
|
||||
|
||||
MiddlewareChain::~MiddlewareChain() {
|
||||
Middleware *current = _root;
|
||||
while (current) {
|
||||
Middleware *next = current->_next;
|
||||
if (current->_freeOnRemoval) {
|
||||
delete current;
|
||||
}
|
||||
current = next;
|
||||
}
|
||||
_root = nullptr;
|
||||
}
|
||||
|
||||
void MiddlewareChain::addMiddleware(Middleware::Function fn) {
|
||||
MiddlewareFunction *middleware = new MiddlewareFunction(fn);
|
||||
middleware->_freeOnRemoval = true;
|
||||
addMiddleware(middleware);
|
||||
}
|
||||
|
||||
void MiddlewareChain::addMiddleware(Middleware *middleware) {
|
||||
if (!_root) {
|
||||
_root = middleware;
|
||||
return;
|
||||
}
|
||||
Middleware *current = _root;
|
||||
while (current->_next) {
|
||||
current = current->_next;
|
||||
}
|
||||
current->_next = middleware;
|
||||
}
|
||||
|
||||
bool MiddlewareChain::removeMiddleware(Middleware *middleware) {
|
||||
if (!_root) {
|
||||
return false;
|
||||
}
|
||||
if (_root == middleware) {
|
||||
_root = _root->_next;
|
||||
if (middleware->_freeOnRemoval) {
|
||||
delete middleware;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
Middleware *current = _root;
|
||||
while (current->_next) {
|
||||
if (current->_next == middleware) {
|
||||
current->_next = current->_next->_next;
|
||||
if (middleware->_freeOnRemoval) {
|
||||
delete middleware;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
current = current->_next;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MiddlewareChain::runChain(WebServer &server, Middleware::Callback finalizer) {
|
||||
if (!_root) {
|
||||
return finalizer();
|
||||
}
|
||||
_current = _root;
|
||||
Middleware::Callback next;
|
||||
next = [this, &server, &next, finalizer]() {
|
||||
if (!_current) {
|
||||
return finalizer();
|
||||
}
|
||||
Middleware *that = _current;
|
||||
_current = _current->_next;
|
||||
return that->run(server, next);
|
||||
};
|
||||
return next();
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
#ifndef URI_BRACES_H
|
||||
#define URI_BRACES_H
|
||||
|
||||
#include "Uri.h"
|
||||
|
||||
class UriBraces : public Uri {
|
||||
|
||||
public:
|
||||
explicit UriBraces(const char *uri) : Uri(uri){};
|
||||
explicit UriBraces(const String &uri) : Uri(uri){};
|
||||
|
||||
Uri *clone() const override final {
|
||||
return new UriBraces(_uri);
|
||||
};
|
||||
|
||||
void initPathArgs(std::vector<String> &pathArgs) override final {
|
||||
int numParams = 0, start = 0;
|
||||
do {
|
||||
start = _uri.indexOf("{}", start);
|
||||
if (start > 0) {
|
||||
numParams++;
|
||||
start += 2;
|
||||
}
|
||||
} while (start > 0);
|
||||
pathArgs.resize(numParams);
|
||||
}
|
||||
|
||||
bool canHandle(const String &requestUri, std::vector<String> &pathArgs) override final {
|
||||
if (Uri::canHandle(requestUri, pathArgs)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t uriLength = _uri.length();
|
||||
unsigned int pathArgIndex = 0;
|
||||
unsigned int requestUriIndex = 0;
|
||||
for (unsigned int i = 0; i < uriLength; i++, requestUriIndex++) {
|
||||
char uriChar = _uri[i];
|
||||
char requestUriChar = requestUri[requestUriIndex];
|
||||
|
||||
if (uriChar == requestUriChar) {
|
||||
continue;
|
||||
}
|
||||
if (uriChar != '{') {
|
||||
return false;
|
||||
}
|
||||
|
||||
i += 2; // index of char after '}'
|
||||
if (i >= uriLength) {
|
||||
// there is no char after '}'
|
||||
pathArgs[pathArgIndex] = requestUri.substring(requestUriIndex);
|
||||
return pathArgs[pathArgIndex].indexOf("/") == -1; // path argument may not contain a '/'
|
||||
} else {
|
||||
char charEnd = _uri[i];
|
||||
int uriIndex = requestUri.indexOf(charEnd, requestUriIndex);
|
||||
if (uriIndex < 0) {
|
||||
return false;
|
||||
}
|
||||
pathArgs[pathArgIndex] = requestUri.substring(requestUriIndex, uriIndex);
|
||||
requestUriIndex = (unsigned int)uriIndex;
|
||||
}
|
||||
pathArgIndex++;
|
||||
}
|
||||
|
||||
return requestUriIndex >= requestUri.length();
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,22 @@
|
||||
#ifndef URI_GLOB_H
|
||||
#define URI_GLOB_H
|
||||
|
||||
#include "Uri.h"
|
||||
#include <fnmatch.h>
|
||||
|
||||
class UriGlob : public Uri {
|
||||
|
||||
public:
|
||||
explicit UriGlob(const char *uri) : Uri(uri){};
|
||||
explicit UriGlob(const String &uri) : Uri(uri){};
|
||||
|
||||
Uri *clone() const override final {
|
||||
return new UriGlob(_uri);
|
||||
};
|
||||
|
||||
bool canHandle(const String &requestUri, __attribute__((unused)) std::vector<String> &pathArgs) override final {
|
||||
return fnmatch(_uri.c_str(), requestUri.c_str(), 0) == 0;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,45 @@
|
||||
#ifndef URI_REGEX_H
|
||||
#define URI_REGEX_H
|
||||
|
||||
#include "Uri.h"
|
||||
#include <regex>
|
||||
|
||||
class UriRegex : public Uri {
|
||||
|
||||
public:
|
||||
explicit UriRegex(const char *uri) : Uri(uri){};
|
||||
explicit UriRegex(const String &uri) : Uri(uri){};
|
||||
|
||||
Uri *clone() const override final {
|
||||
return new UriRegex(_uri);
|
||||
};
|
||||
|
||||
void initPathArgs(std::vector<String> &pathArgs) override final {
|
||||
std::regex rgx((_uri + "|").c_str());
|
||||
std::smatch matches;
|
||||
std::string s{""};
|
||||
std::regex_search(s, matches, rgx);
|
||||
pathArgs.resize(matches.size() - 1);
|
||||
}
|
||||
|
||||
bool canHandle(const String &requestUri, std::vector<String> &pathArgs) override final {
|
||||
if (Uri::canHandle(requestUri, pathArgs)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
unsigned int pathArgIndex = 0;
|
||||
std::regex rgx(_uri.c_str());
|
||||
std::smatch matches;
|
||||
std::string s(requestUri.c_str());
|
||||
if (std::regex_search(s, matches, rgx)) {
|
||||
for (size_t i = 1; i < matches.size(); ++i) { // skip first
|
||||
pathArgs[pathArgIndex] = String(matches[i].str().c_str());
|
||||
pathArgIndex++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user