include wifi and thernet webserver
This commit is contained in:
@@ -11,12 +11,11 @@ include_directories(${PROJECT_NAME}
|
|||||||
lib/nuki_ble/src
|
lib/nuki_ble/src
|
||||||
lib/ESP32_BLE_Arduino-1.0.1/src
|
lib/ESP32_BLE_Arduino-1.0.1/src
|
||||||
lib/WiFiManager
|
lib/WiFiManager
|
||||||
lib/pubsubclient/src/
|
lib/pubsubclient/src
|
||||||
|
lib/EthernetWebServer/src
|
||||||
)
|
)
|
||||||
|
|
||||||
file(GLOB SRCFILES
|
file(GLOB SRCFILES
|
||||||
# "Lib/FreeRTOS/src/*.c"
|
|
||||||
# "Lib/FreeRTOS/src/*.cpp"
|
|
||||||
Pins.h
|
Pins.h
|
||||||
Network.cpp
|
Network.cpp
|
||||||
networkDevices/NetworkDevice.h
|
networkDevices/NetworkDevice.h
|
||||||
@@ -45,6 +44,8 @@ file(GLOB_RECURSE SRCFILESREC
|
|||||||
lib/NimBLE-Arduino/src/*.c
|
lib/NimBLE-Arduino/src/*.c
|
||||||
lib/NimBLE-Arduino/src/*.cpp
|
lib/NimBLE-Arduino/src/*.cpp
|
||||||
lib/NimBLE-Arduino/src/*.h
|
lib/NimBLE-Arduino/src/*.h
|
||||||
|
lib/EthernetWebServer/src/*.cpp
|
||||||
|
lib/EthernetWebServer/src/*.h
|
||||||
)
|
)
|
||||||
|
|
||||||
add_executable(${PROJECT_NAME}
|
add_executable(${PROJECT_NAME}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
#include "Network.h"
|
#include "Network.h"
|
||||||
#include <WiFiManager.h> // https://github.com/tzapu/WiFiManager
|
|
||||||
#include "Arduino.h"
|
#include "Arduino.h"
|
||||||
#include "MqttTopics.h"
|
#include "MqttTopics.h"
|
||||||
#include "PreferencesKeys.h"
|
#include "PreferencesKeys.h"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Preferences.h>
|
#include <Preferences.h>
|
||||||
#include <WebServer.h>
|
#include <EthernetWebServer.h>
|
||||||
#include "NukiWrapper.h"
|
#include "NukiWrapper.h"
|
||||||
#include "Network.h"
|
#include "Network.h"
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ private:
|
|||||||
|
|
||||||
void waitAndProcess(const bool blocking, const uint32_t duration);
|
void waitAndProcess(const bool blocking, const uint32_t duration);
|
||||||
|
|
||||||
WebServer server;
|
EthernetWebServer server;
|
||||||
NukiWrapper* _nuki;
|
NukiWrapper* _nuki;
|
||||||
Network* _network;
|
Network* _network;
|
||||||
Preferences* _preferences;
|
Preferences* _preferences;
|
||||||
|
|||||||
@@ -0,0 +1,147 @@
|
|||||||
|
/*
|
||||||
|
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 <WiFiClient.h>
|
||||||
|
#include <EthernetWebServer.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 min = sec / 60;
|
||||||
|
int hr = min / 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 % 60, sec % 60
|
||||||
|
);
|
||||||
|
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);
|
||||||
|
}
|
||||||
304
lib/EthernetWebServer/examples/FSBrowser/FSBrowser.ino
Normal file
304
lib/EthernetWebServer/examples/FSBrowser/FSBrowser.ino
Normal file
@@ -0,0 +1,304 @@
|
|||||||
|
/*
|
||||||
|
FSWebServer - Example EthernetWebServer with FS backend for esp8266/esp32
|
||||||
|
Copyright (c) 2015 Hristo Gochkov. All rights reserved.
|
||||||
|
This file is part of the EthernetWebServer 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 <WiFiClient.h>
|
||||||
|
#include <EthernetWebServer.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.name()).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
|
||||||
|
}
|
||||||
BIN
lib/EthernetWebServer/examples/FSBrowser/data/edit.htm.gz
Normal file
BIN
lib/EthernetWebServer/examples/FSBrowser/data/edit.htm.gz
Normal file
Binary file not shown.
BIN
lib/EthernetWebServer/examples/FSBrowser/data/favicon.ico
Normal file
BIN
lib/EthernetWebServer/examples/FSBrowser/data/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
BIN
lib/EthernetWebServer/examples/FSBrowser/data/graphs.js.gz
Normal file
BIN
lib/EthernetWebServer/examples/FSBrowser/data/graphs.js.gz
Normal file
Binary file not shown.
97
lib/EthernetWebServer/examples/FSBrowser/data/index.htm
Normal file
97
lib/EthernetWebServer/examples/FSBrowser/data/index.htm
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
<!--
|
||||||
|
FSWebServer - Example Index Page
|
||||||
|
Copyright (c) 2015 Hristo Gochkov. All rights reserved.
|
||||||
|
This file is part of the EthernetWebServer 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>
|
||||||
74
lib/EthernetWebServer/examples/HelloServer/HelloServer.ino
Normal file
74
lib/EthernetWebServer/examples/HelloServer/HelloServer.ino
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
#include <WiFi.h>
|
||||||
|
#include <WiFiClient.h>
|
||||||
|
#include <EthernetWebServer.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,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 <EthernetWebServer.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,42 @@
|
|||||||
|
#include <WiFi.h>
|
||||||
|
#include <ESPmDNS.h>
|
||||||
|
#include <ArduinoOTA.h>
|
||||||
|
#include <EthernetWebServer.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,57 @@
|
|||||||
|
#include <WiFi.h>
|
||||||
|
#include <WiFiClient.h>
|
||||||
|
#include <EthernetWebServer.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
|
||||||
|
}
|
||||||
314
lib/EthernetWebServer/examples/SDWebServer/SDWebServer.ino
Normal file
314
lib/EthernetWebServer/examples/SDWebServer/SDWebServer.ino
Normal file
@@ -0,0 +1,314 @@
|
|||||||
|
/*
|
||||||
|
SDWebServer - Example EthernetWebServer with SD Card backend for esp8266
|
||||||
|
|
||||||
|
Copyright (c) 2015 Hristo Gochkov. All rights reserved.
|
||||||
|
This file is part of the EthernetWebServer 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 charecters are not supported by the SD Library
|
||||||
|
File Names longer than 8 charecters 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
|
||||||
|
|
||||||
|
*/
|
||||||
|
#include <WiFi.h>
|
||||||
|
#include <WiFiClient.h>
|
||||||
|
#include <EthernetWebServer.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", "");
|
||||||
|
WiFiClient client = server.client();
|
||||||
|
|
||||||
|
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.name();
|
||||||
|
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
|
||||||
|
}
|
||||||
674
lib/EthernetWebServer/examples/SDWebServer/SdRoot/edit/index.htm
Normal file
674
lib/EthernetWebServer/examples/SDWebServer/SdRoot/edit/index.htm
Normal file
@@ -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 = (((path == "/")?"":path)+"/"+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 = (((path == "/")?"":path)+"/"+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>
|
||||||
22
lib/EthernetWebServer/examples/SDWebServer/SdRoot/index.htm
Normal file
22
lib/EthernetWebServer/examples/SDWebServer/SdRoot/index.htm
Normal file
@@ -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>
|
||||||
BIN
lib/EthernetWebServer/examples/SDWebServer/SdRoot/pins.png
Normal file
BIN
lib/EthernetWebServer/examples/SDWebServer/SdRoot/pins.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 174 KiB |
@@ -0,0 +1,133 @@
|
|||||||
|
#include <WiFi.h>
|
||||||
|
#include <WiFiClient.h>
|
||||||
|
#include <EthernetWebServer.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("Authentification Successful");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Serial.println("Authentification 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 authentification 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 authentification
|
||||||
|
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 authentification");
|
||||||
|
});
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
69
lib/EthernetWebServer/examples/WebUpdate/WebUpdate.ino
Normal file
69
lib/EthernetWebServer/examples/WebUpdate/WebUpdate.ino
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
To upload through terminal you can use: curl -F "image=@firmware.bin" esp8266-webupdate.local/update
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <WiFi.h>
|
||||||
|
#include <WiFiClient.h>
|
||||||
|
#include <EthernetWebServer.h>
|
||||||
|
#include <ESPmDNS.h>
|
||||||
|
#include <Update.h>
|
||||||
|
|
||||||
|
const char* host = "esp32-webupdate";
|
||||||
|
const char* ssid = "........";
|
||||||
|
const char* password = "........";
|
||||||
|
|
||||||
|
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>";
|
||||||
|
|
||||||
|
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.on("/", HTTP_GET, []() {
|
||||||
|
server.sendHeader("Connection", "close");
|
||||||
|
server.send(200, "text/html", serverIndex);
|
||||||
|
});
|
||||||
|
server.on("/update", HTTP_POST, []() {
|
||||||
|
server.sendHeader("Connection", "close");
|
||||||
|
server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK");
|
||||||
|
ESP.restart();
|
||||||
|
}, []() {
|
||||||
|
HTTPUpload& upload = server.upload();
|
||||||
|
if (upload.status == UPLOAD_FILE_START) {
|
||||||
|
Serial.setDebugOutput(true);
|
||||||
|
Serial.printf("Update: %s\n", upload.filename.c_str());
|
||||||
|
if (!Update.begin()) { //start with max available size
|
||||||
|
Update.printError(Serial);
|
||||||
|
}
|
||||||
|
} else if (upload.status == UPLOAD_FILE_WRITE) {
|
||||||
|
if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
|
||||||
|
Update.printError(Serial);
|
||||||
|
}
|
||||||
|
} else if (upload.status == UPLOAD_FILE_END) {
|
||||||
|
if (Update.end(true)) { //true to set the size to the current progress
|
||||||
|
Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize);
|
||||||
|
} else {
|
||||||
|
Update.printError(Serial);
|
||||||
|
}
|
||||||
|
Serial.setDebugOutput(false);
|
||||||
|
} else {
|
||||||
|
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
|
||||||
|
}
|
||||||
38
lib/EthernetWebServer/keywords.txt
Normal file
38
lib/EthernetWebServer/keywords.txt
Normal file
@@ -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
|
||||||
9
lib/EthernetWebServer/library.properties
Normal file
9
lib/EthernetWebServer/library.properties
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
name=WebServer
|
||||||
|
version=1.0
|
||||||
|
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
|
||||||
29
lib/EthernetWebServer/src/EthernetServerImpl.cpp
Normal file
29
lib/EthernetWebServer/src/EthernetServerImpl.cpp
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#include "EthernetServerImpl.h"
|
||||||
|
|
||||||
|
EthernetServerImpl::EthernetServerImpl(IPAddress addr, int port)
|
||||||
|
: EthernetServer(port)
|
||||||
|
{}
|
||||||
|
|
||||||
|
EthernetServerImpl::EthernetServerImpl(int port)
|
||||||
|
: EthernetServer(port)
|
||||||
|
{}
|
||||||
|
|
||||||
|
void EthernetServerImpl::begin(uint16_t port)
|
||||||
|
{
|
||||||
|
EthernetServer::begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EthernetServerImpl::begin()
|
||||||
|
{
|
||||||
|
EthernetServer::begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EthernetServerImpl::close()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
int EthernetServerImpl::setNoDelay(bool nodelay)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
15
lib/EthernetWebServer/src/EthernetServerImpl.h
Normal file
15
lib/EthernetWebServer/src/EthernetServerImpl.h
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <EthernetServer.h>
|
||||||
|
|
||||||
|
class EthernetServerImpl : public EthernetServer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
EthernetServerImpl(IPAddress addr, int port);
|
||||||
|
EthernetServerImpl(int port);
|
||||||
|
|
||||||
|
void begin();
|
||||||
|
void begin(uint16_t port);
|
||||||
|
void close();
|
||||||
|
int setNoDelay(bool nodelay);
|
||||||
|
};
|
||||||
712
lib/EthernetWebServer/src/EthernetWebServer.cpp
Normal file
712
lib/EthernetWebServer/src/EthernetWebServer.cpp
Normal file
@@ -0,0 +1,712 @@
|
|||||||
|
/*
|
||||||
|
EthernetWebServer.cpp - 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)
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <esp32-hal-log.h>
|
||||||
|
#include <libb64/cencode.h>
|
||||||
|
#include "WiFiServer.h"
|
||||||
|
#include "EthernetClient.h"
|
||||||
|
#include "EthernetWebServer.h"
|
||||||
|
#include "FS.h"
|
||||||
|
#include "detail/RequestHandlersImpl.h"
|
||||||
|
#include "mbedtls/md5.h"
|
||||||
|
|
||||||
|
|
||||||
|
static const char AUTHORIZATION_HEADER[] = "Authorization";
|
||||||
|
static const char qop_auth[] PROGMEM = "qop=auth";
|
||||||
|
static const char qop_auth_quoted[] PROGMEM = "qop=\"auth\"";
|
||||||
|
static const char WWW_Authenticate[] = "WWW-Authenticate";
|
||||||
|
static const char Content_Length[] = "Content-Length";
|
||||||
|
|
||||||
|
|
||||||
|
EthernetWebServer::EthernetWebServer(IPAddress addr, int port)
|
||||||
|
: _corsEnabled(false)
|
||||||
|
, _server(addr, port)
|
||||||
|
, _currentMethod(HTTP_ANY)
|
||||||
|
, _currentVersion(0)
|
||||||
|
, _currentStatus(HC_NONE)
|
||||||
|
, _statusChange(0)
|
||||||
|
, _nullDelay(true)
|
||||||
|
, _currentHandler(nullptr)
|
||||||
|
, _firstHandler(nullptr)
|
||||||
|
, _lastHandler(nullptr)
|
||||||
|
, _currentArgCount(0)
|
||||||
|
, _currentArgs(nullptr)
|
||||||
|
, _postArgsLen(0)
|
||||||
|
, _postArgs(nullptr)
|
||||||
|
, _headerKeysCount(0)
|
||||||
|
, _currentHeaders(nullptr)
|
||||||
|
, _contentLength(0)
|
||||||
|
, _chunked(false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
EthernetWebServer::EthernetWebServer(int port)
|
||||||
|
: _corsEnabled(false)
|
||||||
|
, _server(port)
|
||||||
|
, _currentMethod(HTTP_ANY)
|
||||||
|
, _currentVersion(0)
|
||||||
|
, _currentStatus(HC_NONE)
|
||||||
|
, _statusChange(0)
|
||||||
|
, _nullDelay(true)
|
||||||
|
, _currentHandler(nullptr)
|
||||||
|
, _firstHandler(nullptr)
|
||||||
|
, _lastHandler(nullptr)
|
||||||
|
, _currentArgCount(0)
|
||||||
|
, _currentArgs(nullptr)
|
||||||
|
, _postArgsLen(0)
|
||||||
|
, _postArgs(nullptr)
|
||||||
|
, _headerKeysCount(0)
|
||||||
|
, _currentHeaders(nullptr)
|
||||||
|
, _contentLength(0)
|
||||||
|
, _chunked(false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
EthernetWebServer::~EthernetWebServer() {
|
||||||
|
_server.close();
|
||||||
|
if (_currentHeaders)
|
||||||
|
delete[]_currentHeaders;
|
||||||
|
RequestHandler* handler = _firstHandler;
|
||||||
|
while (handler) {
|
||||||
|
RequestHandler* next = handler->next();
|
||||||
|
delete handler;
|
||||||
|
handler = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EthernetWebServer::begin() {
|
||||||
|
close();
|
||||||
|
_server.begin();
|
||||||
|
_server.setNoDelay(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EthernetWebServer::begin(uint16_t port) {
|
||||||
|
close();
|
||||||
|
_server.begin(port);
|
||||||
|
_server.setNoDelay(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
String EthernetWebServer::_extractParam(String& authReq, const String& param, const char delimit){
|
||||||
|
int _begin = authReq.indexOf(param);
|
||||||
|
if (_begin == -1)
|
||||||
|
return "";
|
||||||
|
return authReq.substring(_begin+param.length(),authReq.indexOf(delimit,_begin+param.length()));
|
||||||
|
}
|
||||||
|
|
||||||
|
static String md5str(String &in){
|
||||||
|
char out[33] = {0};
|
||||||
|
mbedtls_md5_context _ctx;
|
||||||
|
uint8_t i;
|
||||||
|
uint8_t * _buf = (uint8_t*)malloc(16);
|
||||||
|
if(_buf == NULL)
|
||||||
|
return String(out);
|
||||||
|
memset(_buf, 0x00, 16);
|
||||||
|
mbedtls_md5_init(&_ctx);
|
||||||
|
mbedtls_md5_starts(&_ctx);
|
||||||
|
mbedtls_md5_update(&_ctx, (const uint8_t *)in.c_str(), in.length());
|
||||||
|
mbedtls_md5_finish(&_ctx, _buf);
|
||||||
|
for(i = 0; i < 16; i++) {
|
||||||
|
sprintf(out + (i * 2), "%02x", _buf[i]);
|
||||||
|
}
|
||||||
|
out[32] = 0;
|
||||||
|
free(_buf);
|
||||||
|
return String(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EthernetWebServer::authenticate(const char * username, const char * password){
|
||||||
|
if(hasHeader(FPSTR(AUTHORIZATION_HEADER))) {
|
||||||
|
String authReq = header(FPSTR(AUTHORIZATION_HEADER));
|
||||||
|
if(authReq.startsWith(F("Basic"))){
|
||||||
|
authReq = authReq.substring(6);
|
||||||
|
authReq.trim();
|
||||||
|
char toencodeLen = strlen(username)+strlen(password)+1;
|
||||||
|
char *toencode = new char[toencodeLen + 1];
|
||||||
|
if(toencode == NULL){
|
||||||
|
authReq = "";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
char *encoded = new char[base64_encode_expected_len(toencodeLen)+1];
|
||||||
|
if(encoded == NULL){
|
||||||
|
authReq = "";
|
||||||
|
delete[] toencode;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
sprintf(toencode, "%s:%s", username, password);
|
||||||
|
if(base64_encode_chars(toencode, toencodeLen, encoded) > 0 && authReq.equalsConstantTime(encoded)) {
|
||||||
|
authReq = "";
|
||||||
|
delete[] toencode;
|
||||||
|
delete[] encoded;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
delete[] toencode;
|
||||||
|
delete[] encoded;
|
||||||
|
} else if(authReq.startsWith(F("Digest"))) {
|
||||||
|
authReq = authReq.substring(7);
|
||||||
|
log_v("%s", authReq.c_str());
|
||||||
|
String _username = _extractParam(authReq,F("username=\""),'\"');
|
||||||
|
if(!_username.length() || _username != String(username)) {
|
||||||
|
authReq = "";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// extracting required parameters for RFC 2069 simpler Digest
|
||||||
|
String _realm = _extractParam(authReq, F("realm=\""),'\"');
|
||||||
|
String _nonce = _extractParam(authReq, F("nonce=\""),'\"');
|
||||||
|
String _uri = _extractParam(authReq, F("uri=\""),'\"');
|
||||||
|
String _response = _extractParam(authReq, F("response=\""),'\"');
|
||||||
|
String _opaque = _extractParam(authReq, F("opaque=\""),'\"');
|
||||||
|
|
||||||
|
if((!_realm.length()) || (!_nonce.length()) || (!_uri.length()) || (!_response.length()) || (!_opaque.length())) {
|
||||||
|
authReq = "";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if((_opaque != _sopaque) || (_nonce != _snonce) || (_realm != _srealm)) {
|
||||||
|
authReq = "";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// parameters for the RFC 2617 newer Digest
|
||||||
|
String _nc,_cnonce;
|
||||||
|
if(authReq.indexOf(FPSTR(qop_auth)) != -1 || authReq.indexOf(FPSTR(qop_auth_quoted)) != -1) {
|
||||||
|
_nc = _extractParam(authReq, F("nc="), ',');
|
||||||
|
_cnonce = _extractParam(authReq, F("cnonce=\""),'\"');
|
||||||
|
}
|
||||||
|
String _H1 = md5str(String(username) + ':' + _realm + ':' + String(password));
|
||||||
|
log_v("Hash of user:realm:pass=%s", _H1.c_str());
|
||||||
|
String _H2 = "";
|
||||||
|
if(_currentMethod == HTTP_GET){
|
||||||
|
_H2 = md5str(String(F("GET:")) + _uri);
|
||||||
|
}else if(_currentMethod == HTTP_POST){
|
||||||
|
_H2 = md5str(String(F("POST:")) + _uri);
|
||||||
|
}else if(_currentMethod == HTTP_PUT){
|
||||||
|
_H2 = md5str(String(F("PUT:")) + _uri);
|
||||||
|
}else if(_currentMethod == HTTP_DELETE){
|
||||||
|
_H2 = md5str(String(F("DELETE:")) + _uri);
|
||||||
|
}else{
|
||||||
|
_H2 = md5str(String(F("GET:")) + _uri);
|
||||||
|
}
|
||||||
|
log_v("Hash of GET:uri=%s", _H2.c_str());
|
||||||
|
String _responsecheck = "";
|
||||||
|
if(authReq.indexOf(FPSTR(qop_auth)) != -1 || authReq.indexOf(FPSTR(qop_auth_quoted)) != -1) {
|
||||||
|
_responsecheck = md5str(_H1 + ':' + _nonce + ':' + _nc + ':' + _cnonce + F(":auth:") + _H2);
|
||||||
|
} else {
|
||||||
|
_responsecheck = md5str(_H1 + ':' + _nonce + ':' + _H2);
|
||||||
|
}
|
||||||
|
log_v("The Proper response=%s", _responsecheck.c_str());
|
||||||
|
if(_response == _responsecheck){
|
||||||
|
authReq = "";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
authReq = "";
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String EthernetWebServer::_getRandomHexString() {
|
||||||
|
char buffer[33]; // buffer to hold 32 Hex Digit + /0
|
||||||
|
int i;
|
||||||
|
for(i = 0; i < 4; i++) {
|
||||||
|
sprintf (buffer + (i*8), "%08x", esp_random());
|
||||||
|
}
|
||||||
|
return String(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EthernetWebServer::requestAuthentication(HTTPAuthMethod mode, const char* realm, const String& authFailMsg) {
|
||||||
|
if(realm == NULL) {
|
||||||
|
_srealm = String(F("Login Required"));
|
||||||
|
} else {
|
||||||
|
_srealm = String(realm);
|
||||||
|
}
|
||||||
|
if(mode == BASIC_AUTH) {
|
||||||
|
sendHeader(String(FPSTR(WWW_Authenticate)), String(F("Basic realm=\"")) + _srealm + String(F("\"")));
|
||||||
|
} else {
|
||||||
|
_snonce=_getRandomHexString();
|
||||||
|
_sopaque=_getRandomHexString();
|
||||||
|
sendHeader(String(FPSTR(WWW_Authenticate)), String(F("Digest realm=\"")) +_srealm + String(F("\", qop=\"auth\", nonce=\"")) + _snonce + String(F("\", opaque=\"")) + _sopaque + String(F("\"")));
|
||||||
|
}
|
||||||
|
using namespace mime;
|
||||||
|
send(401, String(FPSTR(mimeTable[html].mimeType)), authFailMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EthernetWebServer::on(const Uri &uri, EthernetWebServer::THandlerFunction handler) {
|
||||||
|
on(uri, HTTP_ANY, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EthernetWebServer::on(const Uri &uri, HTTPMethod method, EthernetWebServer::THandlerFunction fn) {
|
||||||
|
on(uri, method, fn, _fileUploadHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EthernetWebServer::on(const Uri &uri, HTTPMethod method, EthernetWebServer::THandlerFunction fn, EthernetWebServer::THandlerFunction ufn) {
|
||||||
|
_addRequestHandler(new FunctionRequestHandler(fn, ufn, uri, method));
|
||||||
|
}
|
||||||
|
|
||||||
|
void EthernetWebServer::addHandler(RequestHandler* handler) {
|
||||||
|
_addRequestHandler(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EthernetWebServer::_addRequestHandler(RequestHandler* handler) {
|
||||||
|
if (!_lastHandler) {
|
||||||
|
_firstHandler = handler;
|
||||||
|
_lastHandler = handler;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
_lastHandler->next(handler);
|
||||||
|
_lastHandler = handler;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EthernetWebServer::serveStatic(const char* uri, FS& fs, const char* path, const char* cache_header) {
|
||||||
|
_addRequestHandler(new StaticRequestHandler(fs, path, uri, cache_header));
|
||||||
|
}
|
||||||
|
|
||||||
|
void EthernetWebServer::handleClient() {
|
||||||
|
if (_currentStatus == HC_NONE) {
|
||||||
|
EthernetClient client = _server.available();
|
||||||
|
if (!client) {
|
||||||
|
if (_nullDelay) {
|
||||||
|
delay(1);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log_v("New client");
|
||||||
|
|
||||||
|
_currentClient = client;
|
||||||
|
_currentStatus = HC_WAIT_READ;
|
||||||
|
_statusChange = millis();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool keepCurrentClient = false;
|
||||||
|
bool callYield = false;
|
||||||
|
|
||||||
|
if (_currentClient.connected()) {
|
||||||
|
switch (_currentStatus) {
|
||||||
|
case HC_NONE:
|
||||||
|
// No-op to avoid C++ compiler warning
|
||||||
|
break;
|
||||||
|
case HC_WAIT_READ:
|
||||||
|
// Wait for data from client to become available
|
||||||
|
if (_currentClient.available()) {
|
||||||
|
if (_parseRequest(_currentClient)) {
|
||||||
|
// because HTTP_MAX_SEND_WAIT is expressed in milliseconds,
|
||||||
|
// it must be divided by 1000
|
||||||
|
_currentClient.setTimeout(HTTP_MAX_SEND_WAIT / 1000);
|
||||||
|
_contentLength = CONTENT_LENGTH_NOT_SET;
|
||||||
|
_handleRequest();
|
||||||
|
|
||||||
|
// Fix for issue with Chrome based browsers: https://github.com/espressif/arduino-esp32/issues/3652
|
||||||
|
// if (_currentClient.connected()) {
|
||||||
|
// _currentStatus = HC_WAIT_CLOSE;
|
||||||
|
// _statusChange = millis();
|
||||||
|
// keepCurrentClient = true;
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
} else { // !_currentClient.available()
|
||||||
|
if (millis() - _statusChange <= HTTP_MAX_DATA_WAIT) {
|
||||||
|
keepCurrentClient = true;
|
||||||
|
}
|
||||||
|
callYield = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case HC_WAIT_CLOSE:
|
||||||
|
// Wait for client to close the connection
|
||||||
|
if (millis() - _statusChange <= HTTP_MAX_CLOSE_WAIT) {
|
||||||
|
keepCurrentClient = true;
|
||||||
|
callYield = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!keepCurrentClient) {
|
||||||
|
_currentClient = EthernetClient();
|
||||||
|
_currentStatus = HC_NONE;
|
||||||
|
_currentUpload.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (callYield) {
|
||||||
|
yield();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EthernetWebServer::close() {
|
||||||
|
_server.close();
|
||||||
|
_currentStatus = HC_NONE;
|
||||||
|
if(!_headerKeysCount)
|
||||||
|
collectHeaders(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EthernetWebServer::stop() {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EthernetWebServer::sendHeader(const String& name, const String& value, bool first) {
|
||||||
|
String headerLine = name;
|
||||||
|
headerLine += F(": ");
|
||||||
|
headerLine += value;
|
||||||
|
headerLine += "\r\n";
|
||||||
|
|
||||||
|
if (first) {
|
||||||
|
_responseHeaders = headerLine + _responseHeaders;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
_responseHeaders += headerLine;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EthernetWebServer::setContentLength(const size_t contentLength) {
|
||||||
|
_contentLength = contentLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EthernetWebServer::enableDelay(boolean value) {
|
||||||
|
_nullDelay = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EthernetWebServer::enableCORS(boolean value) {
|
||||||
|
_corsEnabled = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EthernetWebServer::enableCrossOrigin(boolean value) {
|
||||||
|
enableCORS(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EthernetWebServer::_prepareHeader(String& response, int code, const char* content_type, size_t contentLength) {
|
||||||
|
response = String(F("HTTP/1.")) + String(_currentVersion) + ' ';
|
||||||
|
response += String(code);
|
||||||
|
response += ' ';
|
||||||
|
response += _responseCodeToString(code);
|
||||||
|
response += "\r\n";
|
||||||
|
|
||||||
|
using namespace mime;
|
||||||
|
if (!content_type)
|
||||||
|
content_type = mimeTable[html].mimeType;
|
||||||
|
|
||||||
|
sendHeader(String(F("Content-Type")), String(FPSTR(content_type)), true);
|
||||||
|
if (_contentLength == CONTENT_LENGTH_NOT_SET) {
|
||||||
|
sendHeader(String(FPSTR(Content_Length)), String(contentLength));
|
||||||
|
} else if (_contentLength != CONTENT_LENGTH_UNKNOWN) {
|
||||||
|
sendHeader(String(FPSTR(Content_Length)), String(_contentLength));
|
||||||
|
} else if(_contentLength == CONTENT_LENGTH_UNKNOWN && _currentVersion){ //HTTP/1.1 or above client
|
||||||
|
//let's do chunked
|
||||||
|
_chunked = true;
|
||||||
|
sendHeader(String(F("Accept-Ranges")),String(F("none")));
|
||||||
|
sendHeader(String(F("Transfer-Encoding")),String(F("chunked")));
|
||||||
|
}
|
||||||
|
if (_corsEnabled) {
|
||||||
|
sendHeader(String(FPSTR("Access-Control-Allow-Origin")), String("*"));
|
||||||
|
sendHeader(String(FPSTR("Access-Control-Allow-Methods")), String("*"));
|
||||||
|
sendHeader(String(FPSTR("Access-Control-Allow-Headers")), String("*"));
|
||||||
|
}
|
||||||
|
sendHeader(String(F("Connection")), String(F("close")));
|
||||||
|
|
||||||
|
response += _responseHeaders;
|
||||||
|
response += "\r\n";
|
||||||
|
_responseHeaders = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
void EthernetWebServer::send(int code, const char* content_type, const String& content) {
|
||||||
|
String header;
|
||||||
|
// Can we asume the following?
|
||||||
|
//if(code == 200 && content.length() == 0 && _contentLength == CONTENT_LENGTH_NOT_SET)
|
||||||
|
// _contentLength = CONTENT_LENGTH_UNKNOWN;
|
||||||
|
_prepareHeader(header, code, content_type, content.length());
|
||||||
|
_currentClientWrite(header.c_str(), header.length());
|
||||||
|
if(content.length())
|
||||||
|
sendContent(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EthernetWebServer::send_P(int code, PGM_P content_type, PGM_P content) {
|
||||||
|
size_t contentLength = 0;
|
||||||
|
|
||||||
|
if (content != NULL) {
|
||||||
|
contentLength = strlen_P(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
String header;
|
||||||
|
char type[64];
|
||||||
|
memccpy_P((void*)type, (PGM_VOID_P)content_type, 0, sizeof(type));
|
||||||
|
_prepareHeader(header, code, (const char* )type, contentLength);
|
||||||
|
_currentClientWrite(header.c_str(), header.length());
|
||||||
|
sendContent_P(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EthernetWebServer::send_P(int code, PGM_P content_type, PGM_P content, size_t contentLength) {
|
||||||
|
String header;
|
||||||
|
char type[64];
|
||||||
|
memccpy_P((void*)type, (PGM_VOID_P)content_type, 0, sizeof(type));
|
||||||
|
_prepareHeader(header, code, (const char* )type, contentLength);
|
||||||
|
sendContent(header);
|
||||||
|
sendContent_P(content, contentLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EthernetWebServer::send(int code, char* content_type, const String& content) {
|
||||||
|
send(code, (const char*)content_type, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EthernetWebServer::send(int code, const String& content_type, const String& content) {
|
||||||
|
send(code, (const char*)content_type.c_str(), content);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EthernetWebServer::sendContent(const String& content) {
|
||||||
|
sendContent(content.c_str(), content.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
void EthernetWebServer::sendContent(const char* content, size_t contentLength) {
|
||||||
|
const char * footer = "\r\n";
|
||||||
|
if(_chunked) {
|
||||||
|
char * chunkSize = (char *)malloc(11);
|
||||||
|
if(chunkSize){
|
||||||
|
sprintf(chunkSize, "%x%s", contentLength, footer);
|
||||||
|
_currentClientWrite(chunkSize, strlen(chunkSize));
|
||||||
|
free(chunkSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_currentClientWrite(content, contentLength);
|
||||||
|
if(_chunked){
|
||||||
|
_currentClient.write(footer, 2);
|
||||||
|
if (contentLength == 0) {
|
||||||
|
_chunked = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EthernetWebServer::sendContent_P(PGM_P content) {
|
||||||
|
sendContent_P(content, strlen_P(content));
|
||||||
|
}
|
||||||
|
|
||||||
|
void EthernetWebServer::sendContent_P(PGM_P content, size_t size) {
|
||||||
|
const char * footer = "\r\n";
|
||||||
|
if(_chunked) {
|
||||||
|
char * chunkSize = (char *)malloc(11);
|
||||||
|
if(chunkSize){
|
||||||
|
sprintf(chunkSize, "%x%s", size, footer);
|
||||||
|
_currentClientWrite(chunkSize, strlen(chunkSize));
|
||||||
|
free(chunkSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_currentClientWrite_P(content, size);
|
||||||
|
if(_chunked){
|
||||||
|
_currentClient.write(footer, 2);
|
||||||
|
if (size == 0) {
|
||||||
|
_chunked = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void EthernetWebServer::_streamFileCore(const size_t fileSize, const String & fileName, const String & contentType)
|
||||||
|
{
|
||||||
|
using namespace mime;
|
||||||
|
setContentLength(fileSize);
|
||||||
|
if (fileName.endsWith(String(FPSTR(mimeTable[gz].endsWith))) &&
|
||||||
|
contentType != String(FPSTR(mimeTable[gz].mimeType)) &&
|
||||||
|
contentType != String(FPSTR(mimeTable[none].mimeType))) {
|
||||||
|
sendHeader(F("Content-Encoding"), F("gzip"));
|
||||||
|
}
|
||||||
|
send(200, contentType, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
String EthernetWebServer::pathArg(unsigned int i) {
|
||||||
|
if (_currentHandler != nullptr)
|
||||||
|
return _currentHandler->pathArg(i);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
String EthernetWebServer::arg(String name) {
|
||||||
|
for (int j = 0; j < _postArgsLen; ++j) {
|
||||||
|
if ( _postArgs[j].key == name )
|
||||||
|
return _postArgs[j].value;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < _currentArgCount; ++i) {
|
||||||
|
if ( _currentArgs[i].key == name )
|
||||||
|
return _currentArgs[i].value;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
String EthernetWebServer::arg(int i) {
|
||||||
|
if (i < _currentArgCount)
|
||||||
|
return _currentArgs[i].value;
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
String EthernetWebServer::argName(int i) {
|
||||||
|
if (i < _currentArgCount)
|
||||||
|
return _currentArgs[i].key;
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
int EthernetWebServer::args() {
|
||||||
|
return _currentArgCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EthernetWebServer::hasArg(String name) {
|
||||||
|
for (int j = 0; j < _postArgsLen; ++j) {
|
||||||
|
if (_postArgs[j].key == name)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < _currentArgCount; ++i) {
|
||||||
|
if (_currentArgs[i].key == name)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
String EthernetWebServer::header(String name) {
|
||||||
|
for (int i = 0; i < _headerKeysCount; ++i) {
|
||||||
|
if (_currentHeaders[i].key.equalsIgnoreCase(name))
|
||||||
|
return _currentHeaders[i].value;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
void EthernetWebServer::collectHeaders(const char* headerKeys[], const size_t headerKeysCount) {
|
||||||
|
_headerKeysCount = headerKeysCount + 1;
|
||||||
|
if (_currentHeaders)
|
||||||
|
delete[]_currentHeaders;
|
||||||
|
_currentHeaders = new RequestArgument[_headerKeysCount];
|
||||||
|
_currentHeaders[0].key = FPSTR(AUTHORIZATION_HEADER);
|
||||||
|
for (int i = 1; i < _headerKeysCount; i++){
|
||||||
|
_currentHeaders[i].key = headerKeys[i-1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String EthernetWebServer::header(int i) {
|
||||||
|
if (i < _headerKeysCount)
|
||||||
|
return _currentHeaders[i].value;
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
String EthernetWebServer::headerName(int i) {
|
||||||
|
if (i < _headerKeysCount)
|
||||||
|
return _currentHeaders[i].key;
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
int EthernetWebServer::headers() {
|
||||||
|
return _headerKeysCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EthernetWebServer::hasHeader(String name) {
|
||||||
|
for (int i = 0; i < _headerKeysCount; ++i) {
|
||||||
|
if ((_currentHeaders[i].key.equalsIgnoreCase(name)) && (_currentHeaders[i].value.length() > 0))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String EthernetWebServer::hostHeader() {
|
||||||
|
return _hostHeader;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EthernetWebServer::onFileUpload(THandlerFunction fn) {
|
||||||
|
_fileUploadHandler = fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EthernetWebServer::onNotFound(THandlerFunction fn) {
|
||||||
|
_notFoundHandler = fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EthernetWebServer::_handleRequest() {
|
||||||
|
bool handled = false;
|
||||||
|
if (!_currentHandler){
|
||||||
|
log_e("request handler not found");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
handled = _currentHandler->handle(*this, _currentMethod, _currentUri);
|
||||||
|
if (!handled) {
|
||||||
|
log_e("request handler failed to handle request");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!handled && _notFoundHandler) {
|
||||||
|
_notFoundHandler();
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
if (!handled) {
|
||||||
|
using namespace mime;
|
||||||
|
send(404, String(FPSTR(mimeTable[html].mimeType)), String(F("Not found: ")) + _currentUri);
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
if (handled) {
|
||||||
|
_finalizeResponse();
|
||||||
|
}
|
||||||
|
_currentUri = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void EthernetWebServer::_finalizeResponse() {
|
||||||
|
if (_chunked) {
|
||||||
|
sendContent("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String EthernetWebServer::_responseCodeToString(int code) {
|
||||||
|
switch (code) {
|
||||||
|
case 100: return F("Continue");
|
||||||
|
case 101: return F("Switching Protocols");
|
||||||
|
case 200: return F("OK");
|
||||||
|
case 201: return F("Created");
|
||||||
|
case 202: return F("Accepted");
|
||||||
|
case 203: return F("Non-Authoritative Information");
|
||||||
|
case 204: return F("No Content");
|
||||||
|
case 205: return F("Reset Content");
|
||||||
|
case 206: return F("Partial Content");
|
||||||
|
case 300: return F("Multiple Choices");
|
||||||
|
case 301: return F("Moved Permanently");
|
||||||
|
case 302: return F("Found");
|
||||||
|
case 303: return F("See Other");
|
||||||
|
case 304: return F("Not Modified");
|
||||||
|
case 305: return F("Use Proxy");
|
||||||
|
case 307: return F("Temporary Redirect");
|
||||||
|
case 400: return F("Bad Request");
|
||||||
|
case 401: return F("Unauthorized");
|
||||||
|
case 402: return F("Payment Required");
|
||||||
|
case 403: return F("Forbidden");
|
||||||
|
case 404: return F("Not Found");
|
||||||
|
case 405: return F("Method Not Allowed");
|
||||||
|
case 406: return F("Not Acceptable");
|
||||||
|
case 407: return F("Proxy Authentication Required");
|
||||||
|
case 408: return F("Request Time-out");
|
||||||
|
case 409: return F("Conflict");
|
||||||
|
case 410: return F("Gone");
|
||||||
|
case 411: return F("Length Required");
|
||||||
|
case 412: return F("Precondition Failed");
|
||||||
|
case 413: return F("Request Entity Too Large");
|
||||||
|
case 414: return F("Request-URI Too Large");
|
||||||
|
case 415: return F("Unsupported Media Type");
|
||||||
|
case 416: return F("Requested range not satisfiable");
|
||||||
|
case 417: return F("Expectation Failed");
|
||||||
|
case 500: return F("Internal Server Error");
|
||||||
|
case 501: return F("Not Implemented");
|
||||||
|
case 502: return F("Bad Gateway");
|
||||||
|
case 503: return F("Service Unavailable");
|
||||||
|
case 504: return F("Gateway Time-out");
|
||||||
|
case 505: return F("HTTP Version not supported");
|
||||||
|
default: return F("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t EthernetWebServer::write_P(const char *buf, size_t size)
|
||||||
|
{
|
||||||
|
return _currentClient.write(buf, size);
|
||||||
|
}
|
||||||
214
lib/EthernetWebServer/src/EthernetWebServer.h
Normal file
214
lib/EthernetWebServer/src/EthernetWebServer.h
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
/*
|
||||||
|
EthernetWebServer.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 <WiFi.h>
|
||||||
|
#include <EthernetServer.h>
|
||||||
|
#include "HTTP_Method.h"
|
||||||
|
#include "Uri.h"
|
||||||
|
|
||||||
|
enum HTTPUploadStatus { UPLOAD_FILE_START, UPLOAD_FILE_WRITE, UPLOAD_FILE_END,
|
||||||
|
UPLOAD_FILE_ABORTED };
|
||||||
|
enum HTTPClientStatus { HC_NONE, HC_WAIT_READ, HC_WAIT_CLOSE };
|
||||||
|
enum HTTPAuthMethod { BASIC_AUTH, DIGEST_AUTH };
|
||||||
|
|
||||||
|
#define HTTP_DOWNLOAD_UNIT_SIZE 1436
|
||||||
|
|
||||||
|
#ifndef HTTP_UPLOAD_BUFLEN
|
||||||
|
#define HTTP_UPLOAD_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 2000 //ms to wait for the client to close the connection
|
||||||
|
|
||||||
|
#define CONTENT_LENGTH_UNKNOWN ((size_t) -1)
|
||||||
|
#define CONTENT_LENGTH_NOT_SET ((size_t) -2)
|
||||||
|
|
||||||
|
class EthernetWebServer;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
#include "detail/RequestHandler.h"
|
||||||
|
#include "EthernetServerImpl.h"
|
||||||
|
|
||||||
|
namespace fs {
|
||||||
|
class FS;
|
||||||
|
}
|
||||||
|
|
||||||
|
class EthernetWebServer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
EthernetWebServer(IPAddress addr, int port = 80);
|
||||||
|
EthernetWebServer(int port = 80);
|
||||||
|
virtual ~EthernetWebServer();
|
||||||
|
|
||||||
|
virtual void begin();
|
||||||
|
virtual void begin(uint16_t port);
|
||||||
|
virtual void handleClient();
|
||||||
|
|
||||||
|
virtual void close();
|
||||||
|
void stop();
|
||||||
|
|
||||||
|
bool authenticate(const char * username, const char * password);
|
||||||
|
void requestAuthentication(HTTPAuthMethod mode = BASIC_AUTH, const char* realm = NULL, const String& authFailMsg = String("") );
|
||||||
|
|
||||||
|
typedef std::function<void(void)> THandlerFunction;
|
||||||
|
void on(const Uri &uri, THandlerFunction handler);
|
||||||
|
void on(const Uri &uri, HTTPMethod method, THandlerFunction fn);
|
||||||
|
void on(const Uri &uri, HTTPMethod method, THandlerFunction fn, THandlerFunction ufn);
|
||||||
|
void addHandler(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 fn); //handle file uploads
|
||||||
|
|
||||||
|
String uri() { return _currentUri; }
|
||||||
|
HTTPMethod method() { return _currentMethod; }
|
||||||
|
virtual EthernetClient client() { return _currentClient; }
|
||||||
|
HTTPUpload& upload() { return *_currentUpload; }
|
||||||
|
|
||||||
|
String pathArg(unsigned int i); // get request path argument by number
|
||||||
|
String arg(String name); // get request argument value by name
|
||||||
|
String arg(int i); // get request argument value by number
|
||||||
|
String argName(int i); // get request argument name by number
|
||||||
|
int args(); // get arguments count
|
||||||
|
bool hasArg(String name); // check if argument exists
|
||||||
|
void collectHeaders(const char* headerKeys[], const size_t headerKeysCount); // set the request headers to collect
|
||||||
|
String header(String name); // get request header value by name
|
||||||
|
String header(int i); // get request header value by number
|
||||||
|
String headerName(int i); // get request header name by number
|
||||||
|
int headers(); // get header count
|
||||||
|
bool hasHeader(String name); // check if header exists
|
||||||
|
|
||||||
|
String hostHeader(); // get request host header if available or empty String if not
|
||||||
|
|
||||||
|
// 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_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);
|
||||||
|
|
||||||
|
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) {
|
||||||
|
_streamFileCore(file.size(), file.name(), contentType);
|
||||||
|
return _currentClient.write(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
size_t write_P(PGM_P buf, size_t size);
|
||||||
|
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 write_P( b, l ); }
|
||||||
|
void _addRequestHandler(RequestHandler* handler);
|
||||||
|
void _handleRequest();
|
||||||
|
void _finalizeResponse();
|
||||||
|
bool _parseRequest(EthernetClient& client);
|
||||||
|
void _parseArguments(String data);
|
||||||
|
static String _responseCodeToString(int code);
|
||||||
|
bool _parseForm(EthernetClient& client, String boundary, uint32_t len);
|
||||||
|
bool _parseFormUploadAborted();
|
||||||
|
void _uploadWriteByte(uint8_t b);
|
||||||
|
int _uploadReadByte(EthernetClient& 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);
|
||||||
|
|
||||||
|
String _getRandomHexString();
|
||||||
|
// for extracting Auth parameters
|
||||||
|
String _extractParam(String& authReq,const String& param,const char delimit = '"');
|
||||||
|
|
||||||
|
struct RequestArgument {
|
||||||
|
String key;
|
||||||
|
String value;
|
||||||
|
};
|
||||||
|
|
||||||
|
boolean _corsEnabled;
|
||||||
|
EthernetServerImpl _server;
|
||||||
|
|
||||||
|
EthernetClient _currentClient;
|
||||||
|
HTTPMethod _currentMethod;
|
||||||
|
String _currentUri;
|
||||||
|
uint8_t _currentVersion;
|
||||||
|
HTTPClientStatus _currentStatus;
|
||||||
|
unsigned long _statusChange;
|
||||||
|
boolean _nullDelay;
|
||||||
|
|
||||||
|
RequestHandler* _currentHandler;
|
||||||
|
RequestHandler* _firstHandler;
|
||||||
|
RequestHandler* _lastHandler;
|
||||||
|
THandlerFunction _notFoundHandler;
|
||||||
|
THandlerFunction _fileUploadHandler;
|
||||||
|
|
||||||
|
int _currentArgCount;
|
||||||
|
RequestArgument* _currentArgs;
|
||||||
|
int _postArgsLen;
|
||||||
|
RequestArgument* _postArgs;
|
||||||
|
|
||||||
|
std::unique_ptr<HTTPUpload> _currentUpload;
|
||||||
|
|
||||||
|
int _headerKeysCount;
|
||||||
|
RequestArgument* _currentHeaders;
|
||||||
|
size_t _contentLength;
|
||||||
|
String _responseHeaders;
|
||||||
|
|
||||||
|
String _hostHeader;
|
||||||
|
bool _chunked;
|
||||||
|
|
||||||
|
String _snonce; // Store noance and opaque for future comparison
|
||||||
|
String _sopaque;
|
||||||
|
String _srealm; // Store the Auth realm between Calls
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif //ESP8266WEBSERVER_H
|
||||||
9
lib/EthernetWebServer/src/HTTP_Method.h
Normal file
9
lib/EthernetWebServer/src/HTTP_Method.h
Normal file
@@ -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_ */
|
||||||
589
lib/EthernetWebServer/src/Parsing.cpp
Normal file
589
lib/EthernetWebServer/src/Parsing.cpp
Normal file
@@ -0,0 +1,589 @@
|
|||||||
|
/*
|
||||||
|
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 "WiFiServer.h"
|
||||||
|
#include "EthernetClient.h"
|
||||||
|
#include "EthernetWebServer.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)
|
||||||
|
const char * _eth_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(EthernetClient& 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 EthernetWebServer::_parseRequest(EthernetClient& client) {
|
||||||
|
// Read the first line of HTTP request
|
||||||
|
String req = client.readStringUntil('\r');
|
||||||
|
client.readStringUntil('\n');
|
||||||
|
//reset header value
|
||||||
|
for (int i = 0; i < _headerKeysCount; ++i) {
|
||||||
|
_currentHeaders[i].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;
|
||||||
|
|
||||||
|
HTTPMethod method = HTTP_ANY;
|
||||||
|
size_t num_methods = sizeof(_eth_http_method_str) / sizeof(const char *);
|
||||||
|
for (size_t i=0; i<num_methods; i++) {
|
||||||
|
if (methodStr == _eth_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(_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;
|
||||||
|
uint32_t contentLength = 0;
|
||||||
|
//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());
|
||||||
|
|
||||||
|
log_v("headerName: %s", headerName.c_str());
|
||||||
|
log_v("headerValue: %s", 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"))){
|
||||||
|
contentLength = headerValue.toInt();
|
||||||
|
} else if (headerName.equalsIgnoreCase(F("Host"))){
|
||||||
|
_hostHeader = headerValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isForm){
|
||||||
|
size_t plainLength;
|
||||||
|
char* plainBuf = readBytesWithTimeout(client, contentLength, plainLength, HTTP_MAX_POST_WAIT);
|
||||||
|
if (plainLength < contentLength) {
|
||||||
|
free(plainBuf);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (contentLength > 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isForm){
|
||||||
|
_parseArguments(searchStr);
|
||||||
|
if (!_parseForm(client, boundaryStr, contentLength)) {
|
||||||
|
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());
|
||||||
|
|
||||||
|
log_v("headerName: %s", headerName.c_str());
|
||||||
|
log_v("headerValue: %s", headerValue.c_str());
|
||||||
|
|
||||||
|
if (headerName.equalsIgnoreCase("Host")){
|
||||||
|
_hostHeader = headerValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_parseArguments(searchStr);
|
||||||
|
}
|
||||||
|
client.flush();
|
||||||
|
|
||||||
|
log_v("Request: %s", url.c_str());
|
||||||
|
log_v(" Arguments: %s", searchStr.c_str());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EthernetWebServer::_collectHeader(const char* headerName, const char* headerValue) {
|
||||||
|
for (int i = 0; i < _headerKeysCount; i++) {
|
||||||
|
if (_currentHeaders[i].key.equalsIgnoreCase(headerName)) {
|
||||||
|
_currentHeaders[i].value=headerValue;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EthernetWebServer::_parseArguments(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 EthernetWebServer::_uploadWriteByte(uint8_t b){
|
||||||
|
if (_currentUpload->currentSize == HTTP_UPLOAD_BUFLEN){
|
||||||
|
if(_currentHandler && _currentHandler->canUpload(_currentUri))
|
||||||
|
_currentHandler->upload(*this, _currentUri, *_currentUpload);
|
||||||
|
_currentUpload->totalSize += _currentUpload->currentSize;
|
||||||
|
_currentUpload->currentSize = 0;
|
||||||
|
}
|
||||||
|
_currentUpload->buf[_currentUpload->currentSize++] = b;
|
||||||
|
}
|
||||||
|
|
||||||
|
int EthernetWebServer::_uploadReadByte(EthernetClient& client){
|
||||||
|
int res = client.read();
|
||||||
|
if(res < 0) {
|
||||||
|
// keep trying until you either read a valid byte or timeout
|
||||||
|
unsigned long startMillis = millis();
|
||||||
|
long timeoutIntervalMillis = client.getTimeout();
|
||||||
|
boolean 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 EthernetWebServer::_parseForm(EthernetClient& client, String boundary, uint32_t len){
|
||||||
|
(void) len;
|
||||||
|
log_v("Parse Form: Boundary: %s Length: %d", 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() > 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');
|
||||||
|
if (line.length() > 12 && line.substring(0, 12).equalsIgnoreCase(FPSTR(Content_Type))){
|
||||||
|
argType = line.substring(line.indexOf(':')+2);
|
||||||
|
//skip next 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() > 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(_currentUri))
|
||||||
|
_currentHandler->upload(*this, _currentUri, *_currentUpload);
|
||||||
|
_currentUpload->status = UPLOAD_FILE_WRITE;
|
||||||
|
int argByte = _uploadReadByte(client);
|
||||||
|
readfile:
|
||||||
|
|
||||||
|
while(argByte != 0x0D){
|
||||||
|
if(argByte < 0) return _parseFormUploadAborted();
|
||||||
|
_uploadWriteByte(argByte);
|
||||||
|
argByte = _uploadReadByte(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
argByte = _uploadReadByte(client);
|
||||||
|
if(argByte < 0) return _parseFormUploadAborted();
|
||||||
|
if (argByte == 0x0A){
|
||||||
|
argByte = _uploadReadByte(client);
|
||||||
|
if(argByte < 0) return _parseFormUploadAborted();
|
||||||
|
if ((char)argByte != '-'){
|
||||||
|
//continue reading the file
|
||||||
|
_uploadWriteByte(0x0D);
|
||||||
|
_uploadWriteByte(0x0A);
|
||||||
|
goto readfile;
|
||||||
|
} else {
|
||||||
|
argByte = _uploadReadByte(client);
|
||||||
|
if(argByte < 0) return _parseFormUploadAborted();
|
||||||
|
if ((char)argByte != '-'){
|
||||||
|
//continue reading the file
|
||||||
|
_uploadWriteByte(0x0D);
|
||||||
|
_uploadWriteByte(0x0A);
|
||||||
|
_uploadWriteByte((uint8_t)('-'));
|
||||||
|
goto readfile;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t endBuf[boundary.length()];
|
||||||
|
uint32_t i = 0;
|
||||||
|
while(i < boundary.length()){
|
||||||
|
argByte = _uploadReadByte(client);
|
||||||
|
if(argByte < 0) return _parseFormUploadAborted();
|
||||||
|
if ((char)argByte == 0x0D){
|
||||||
|
_uploadWriteByte(0x0D);
|
||||||
|
_uploadWriteByte(0x0A);
|
||||||
|
_uploadWriteByte((uint8_t)('-'));
|
||||||
|
_uploadWriteByte((uint8_t)('-'));
|
||||||
|
uint32_t j = 0;
|
||||||
|
while(j < i){
|
||||||
|
_uploadWriteByte(endBuf[j++]);
|
||||||
|
}
|
||||||
|
goto readfile;
|
||||||
|
}
|
||||||
|
endBuf[i++] = (uint8_t)argByte;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strstr((const char*)endBuf, boundary.c_str()) != NULL){
|
||||||
|
if(_currentHandler && _currentHandler->canUpload(_currentUri))
|
||||||
|
_currentHandler->upload(*this, _currentUri, *_currentUpload);
|
||||||
|
_currentUpload->totalSize += _currentUpload->currentSize;
|
||||||
|
_currentUpload->status = UPLOAD_FILE_END;
|
||||||
|
if(_currentHandler && _currentHandler->canUpload(_currentUri))
|
||||||
|
_currentHandler->upload(*this, _currentUri, *_currentUpload);
|
||||||
|
log_v("End File: %s Type: %s Size: %d", _currentUpload->filename.c_str(), _currentUpload->type.c_str(), _currentUpload->totalSize);
|
||||||
|
line = client.readStringUntil(0x0D);
|
||||||
|
client.readStringUntil(0x0A);
|
||||||
|
if (line == "--"){
|
||||||
|
log_v("Done Parsing POST");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
_uploadWriteByte(0x0D);
|
||||||
|
_uploadWriteByte(0x0A);
|
||||||
|
_uploadWriteByte((uint8_t)('-'));
|
||||||
|
_uploadWriteByte((uint8_t)('-'));
|
||||||
|
uint32_t i = 0;
|
||||||
|
while(i < boundary.length()){
|
||||||
|
_uploadWriteByte(endBuf[i++]);
|
||||||
|
}
|
||||||
|
argByte = _uploadReadByte(client);
|
||||||
|
goto readfile;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_uploadWriteByte(0x0D);
|
||||||
|
goto readfile;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 EthernetWebServer::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 EthernetWebServer::_parseFormUploadAborted(){
|
||||||
|
_currentUpload->status = UPLOAD_FILE_ABORTED;
|
||||||
|
if(_currentHandler && _currentHandler->canUpload(_currentUri))
|
||||||
|
_currentHandler->upload(*this, _currentUri, *_currentUpload);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
29
lib/EthernetWebServer/src/Uri.h
Normal file
29
lib/EthernetWebServer/src/Uri.h
Normal file
@@ -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(String(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
|
||||||
31
lib/EthernetWebServer/src/detail/RequestHandler.h
Normal file
31
lib/EthernetWebServer/src/detail/RequestHandler.h
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
#ifndef REQUESTHANDLER_H
|
||||||
|
#define REQUESTHANDLER_H
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
class RequestHandler {
|
||||||
|
public:
|
||||||
|
virtual ~RequestHandler() { }
|
||||||
|
virtual bool canHandle(HTTPMethod method, String uri) { (void) method; (void) uri; return false; }
|
||||||
|
virtual bool canUpload(String uri) { (void) uri; return false; }
|
||||||
|
virtual bool handle(EthernetWebServer& server, HTTPMethod requestMethod, String requestUri) { (void) server; (void) requestMethod; (void) requestUri; return false; }
|
||||||
|
virtual void upload(EthernetWebServer& server, String requestUri, HTTPUpload& upload) { (void) server; (void) requestUri; (void) upload; }
|
||||||
|
|
||||||
|
RequestHandler* next() { return _next; }
|
||||||
|
void next(RequestHandler* r) { _next = r; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
RequestHandler* _next = nullptr;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::vector<String> pathArgs;
|
||||||
|
|
||||||
|
public:
|
||||||
|
const String& pathArg(unsigned int i) {
|
||||||
|
assert(i < pathArgs.size());
|
||||||
|
return pathArgs[i];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //REQUESTHANDLER_H
|
||||||
151
lib/EthernetWebServer/src/detail/RequestHandlersImpl.h
Normal file
151
lib/EthernetWebServer/src/detail/RequestHandlersImpl.h
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
#ifndef REQUESTHANDLERSIMPL_H
|
||||||
|
#define REQUESTHANDLERSIMPL_H
|
||||||
|
|
||||||
|
#include "RequestHandler.h"
|
||||||
|
#include "mimetable.h"
|
||||||
|
#include "WString.h"
|
||||||
|
#include "Uri.h"
|
||||||
|
|
||||||
|
using namespace mime;
|
||||||
|
|
||||||
|
class FunctionRequestHandler : public RequestHandler {
|
||||||
|
public:
|
||||||
|
FunctionRequestHandler(EthernetWebServer::THandlerFunction fn, EthernetWebServer::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, String requestUri) override {
|
||||||
|
if (_method != HTTP_ANY && _method != requestMethod)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return _uri->canHandle(requestUri, pathArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool canUpload(String requestUri) override {
|
||||||
|
if (!_ufn || !canHandle(HTTP_POST, requestUri))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool handle(EthernetWebServer& server, HTTPMethod requestMethod, String requestUri) override {
|
||||||
|
(void) server;
|
||||||
|
if (!canHandle(requestMethod, requestUri))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
_fn();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void upload(EthernetWebServer& server, String requestUri, HTTPUpload& upload) override {
|
||||||
|
(void) server;
|
||||||
|
(void) upload;
|
||||||
|
if (canUpload(requestUri))
|
||||||
|
_ufn();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
EthernetWebServer::THandlerFunction _fn;
|
||||||
|
EthernetWebServer::THandlerFunction _ufn;
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
_isFile = fs.exists(path);
|
||||||
|
log_v("StaticRequestHandler: path=%s uri=%s isFile=%d, cache_header=%s\r\n", path, uri, _isFile, cache_header);
|
||||||
|
_baseUriLength = _uri.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool canHandle(HTTPMethod requestMethod, String requestUri) override {
|
||||||
|
if (requestMethod != HTTP_GET)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if ((_isFile && requestUri != _uri) || !requestUri.startsWith(_uri))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool handle(EthernetWebServer& server, HTTPMethod requestMethod, String requestUri) override {
|
||||||
|
if (!canHandle(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("/"))
|
||||||
|
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;
|
||||||
|
|
||||||
|
if (_cache_header.length() != 0)
|
||||||
|
server.sendHeader("Cache-Control", _cache_header);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
FS _fs;
|
||||||
|
String _uri;
|
||||||
|
String _path;
|
||||||
|
String _cache_header;
|
||||||
|
bool _isFile;
|
||||||
|
size_t _baseUriLength;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif //REQUESTHANDLERSIMPL_H
|
||||||
35
lib/EthernetWebServer/src/detail/mimetable.cpp
Normal file
35
lib/EthernetWebServer/src/detail/mimetable.cpp
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
#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" },
|
||||||
|
{ ".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" }
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
47
lib/EthernetWebServer/src/detail/mimetable.h
Normal file
47
lib/EthernetWebServer/src/detail/mimetable.h
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
#ifndef __MIMETABLE_H__
|
||||||
|
#define __MIMETABLE_H__
|
||||||
|
|
||||||
|
|
||||||
|
namespace mime
|
||||||
|
{
|
||||||
|
|
||||||
|
enum type
|
||||||
|
{
|
||||||
|
html,
|
||||||
|
htm,
|
||||||
|
css,
|
||||||
|
txt,
|
||||||
|
js,
|
||||||
|
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];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
66
lib/EthernetWebServer/src/uri/UriBraces.h
Normal file
66
lib/EthernetWebServer/src/uri/UriBraces.h
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
#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
|
||||||
22
lib/EthernetWebServer/src/uri/UriGlob.h
Normal file
22
lib/EthernetWebServer/src/uri/UriGlob.h
Normal file
@@ -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
|
||||||
44
lib/EthernetWebServer/src/uri/UriRegex.h
Normal file
44
lib/EthernetWebServer/src/uri/UriRegex.h
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
#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
|
||||||
4
main.cpp
4
main.cpp
@@ -85,8 +85,8 @@ void setup()
|
|||||||
|
|
||||||
Serial.begin(115200);
|
Serial.begin(115200);
|
||||||
|
|
||||||
const NetworkDeviceType networkDevice = NetworkDeviceType::WiFi;
|
// const NetworkDeviceType networkDevice = NetworkDeviceType::WiFi;
|
||||||
// const NetworkDeviceType networkDevice = digitalRead(NETWORK_SELECT) == HIGH ? NetworkDeviceType::WiFi : NetworkDeviceType::W5500;
|
const NetworkDeviceType networkDevice = digitalRead(NETWORK_SELECT) == HIGH ? NetworkDeviceType::WiFi : NetworkDeviceType::W5500;
|
||||||
|
|
||||||
preferences = new Preferences();
|
preferences = new Preferences();
|
||||||
preferences->begin("nukihub", false);
|
preferences->begin("nukihub", false);
|
||||||
|
|||||||
Reference in New Issue
Block a user