Repository: esp8266/ESPWebServer Branch: master Commit: 61cc8117df32 Files: 17 Total size: 103.3 KB Directory structure: gitextract_95h63_sv/ ├── examples/ │ ├── AdvancedWebServer/ │ │ └── AdvancedWebServer.ino │ ├── FSBrowser/ │ │ ├── FSBrowser.ino │ │ └── data/ │ │ └── index.htm │ ├── HelloServer/ │ │ └── HelloServer.ino │ ├── HttpBasicAuth/ │ │ └── HttpBasicAuth.ino │ ├── SDWebServer/ │ │ ├── SDWebServer.ino │ │ └── SdRoot/ │ │ ├── edit/ │ │ │ └── index.htm │ │ └── index.htm │ ├── SimpleAuthentification/ │ │ └── SimpleAuthentification.ino │ └── WebUpdate/ │ └── WebUpdate.ino ├── keywords.txt ├── library.properties └── src/ ├── ESP8266WebServer.cpp ├── ESP8266WebServer.h ├── Parsing.cpp └── detail/ ├── RequestHandler.h └── RequestHandlersImpl.h ================================================ FILE CONTENTS ================================================ ================================================ FILE: examples/AdvancedWebServer/AdvancedWebServer.ino ================================================ /* * 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 #include #include #include const char *ssid = "YourSSIDHere"; const char *password = "YourPSKHere"; ESP8266WebServer 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, "\ \ \ ESP8266 Demo\ \ \ \

Hello from ESP8266!

\

Uptime: %02d:%02d:%02d

\ \ \ ", 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.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 ( "esp8266" ) ) { 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(); } void drawGraph() { String out = ""; char temp[100]; out += "\n"; out += "\n"; out += "\n"; int y = rand() % 130; for (int x = 10; x < 390; x+= 10) { int y2 = rand() % 130; sprintf(temp, "\n", x, 140 - y, x + 10, 140 - y2); out += temp; y = y2; } out += "\n\n"; server.send ( 200, "image/svg+xml", out); } ================================================ FILE: examples/FSBrowser/FSBrowser.ino ================================================ /* FSWebServer - Example WebServer with SPIFFS backend for esp8266 Copyright (c) 2015 Hristo Gochkov. All rights reserved. This file is part of the ESP8266WebServer 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 ("ESP8266 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" esp8266fs.local/edit; done access the sample web page at http://esp8266fs.local edit the page by going to http://esp8266fs.local/edit */ #include #include #include #include #include #define DBG_OUTPUT_PORT Serial const char* ssid = "wifi-ssid"; const char* password = "wifi-password"; const char* host = "esp8266fs"; ESP8266WebServer 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 handleFileRead(String path){ DBG_OUTPUT_PORT.println("handleFileRead: " + path); if(path.endsWith("/")) path += "index.htm"; String contentType = getContentType(path); String pathWithGz = path + ".gz"; if(SPIFFS.exists(pathWithGz) || SPIFFS.exists(path)){ if(SPIFFS.exists(pathWithGz)) path += ".gz"; File file = SPIFFS.open(path, "r"); size_t sent = 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 = SPIFFS.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(!SPIFFS.exists(path)) return server.send(404, "text/plain", "FileNotFound"); SPIFFS.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(SPIFFS.exists(path)) return server.send(500, "text/plain", "FILE EXISTS"); File file = SPIFFS.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); Dir dir = SPIFFS.openDir(path); path = String(); String output = "["; while(dir.next()){ File entry = dir.openFile("r"); if (output != "[") output += ','; bool isDir = false; output += "{\"type\":\""; output += (isDir)?"dir":"file"; output += "\",\"name\":\""; output += String(entry.name()).substring(1); output += "\"}"; entry.close(); } 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); SPIFFS.begin(); { Dir dir = SPIFFS.openDir("/"); while (dir.next()) { String fileName = dir.fileName(); size_t fileSize = dir.fileSize(); DBG_OUTPUT_PORT.printf("FS File: %s, size: %s\n", fileName.c_str(), formatBytes(fileSize).c_str()); } DBG_OUTPUT_PORT.printf("\n"); } //WIFI INIT DBG_OUTPUT_PORT.printf("Connecting to %s\n", ssid); if (String(WiFi.SSID()) != String(ssid)) { 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 SPIFFS 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)(((GPI | GPO) & 0xFFFF) | ((GP16I & 0x01) << 16))); json += "}"; server.send(200, "text/json", json); json = String(); }); server.begin(); DBG_OUTPUT_PORT.println("HTTP server started"); } void loop(void){ server.handleClient(); } ================================================ FILE: examples/FSBrowser/data/index.htm ================================================ ESP Monitor
================================================ FILE: examples/HelloServer/HelloServer.ino ================================================ #include #include #include #include const char* ssid = "........"; const char* password = "........"; ESP8266WebServer server(80); const int led = 13; void handleRoot() { digitalWrite(led, 1); server.send(200, "text/plain", "hello from esp8266!"); 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 #include #include #include const char* ssid = "........"; const char* password = "........"; ESP8266WebServer server(80); const char* www_username = "admin"; const char* www_password = "esp8266"; 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(); } ================================================ FILE: examples/SDWebServer/SDWebServer.ino ================================================ /* SDWebServer - Example WebServer with SD Card backend for esp8266 Copyright (c) 2015 Hristo Gochkov. All rights reserved. This file is part of the ESP8266WebServer 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 #include #include #include #include #include #define DBG_OUTPUT_PORT Serial const char* ssid = "**********"; const char* password = "**********"; const char* host = "esp8266sd"; ESP8266WebServer 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((const char *)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 SD Editor
================================================ FILE: examples/SDWebServer/SdRoot/index.htm ================================================ ESP Index

ESP8266 Pin Functions

================================================ FILE: examples/SimpleAuthentification/SimpleAuthentification.ino ================================================ #include #include #include const char* ssid = "........"; const char* password = "........"; ESP8266WebServer 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 = "
To log in, please use : admin/admin
"; content += "User:
"; content += "Password:
"; content += "
" + msg + "
"; content += "You also can go here"; 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 = "

hello, you successfully connected to esp8266!


"; if (server.hasHeader("User-Agent")){ content += "the user agent used is : " + server.header("User-Agent") + "

"; } content += "You can access this page until you disconnect"; 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 #include #include #include const char* host = "esp8266-webupdate"; const char* ssid = "........"; const char* password = "........"; ESP8266WebServer server(80); const char* serverIndex = "
"; 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); WiFiUDP::stopAll(); Serial.printf("Update: %s\n", upload.filename.c_str()); uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000; if(!Update.begin(maxSketchSpace)){//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); } yield(); }); 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(1); } ================================================ FILE: keywords.txt ================================================ ####################################### # Syntax Coloring Map For Ultrasound ####################################### ####################################### # Datatypes (KEYWORD1) ####################################### ESP8266WebServer 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 ================================================ FILE: library.properties ================================================ name=ESP8266WebServer version=1.0 author=Ivan Grokhotkov maintainer=Ivan Grokhtkov 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=esp8266 ================================================ FILE: src/ESP8266WebServer.cpp ================================================ /* ESP8266WebServer.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 #include #include "WiFiServer.h" #include "WiFiClient.h" #include "ESP8266WebServer.h" #include "FS.h" #include "detail/RequestHandlersImpl.h" //#define DEBUG_ESP_HTTP_SERVER #ifdef DEBUG_ESP_PORT #define DEBUG_OUTPUT DEBUG_ESP_PORT #else #define DEBUG_OUTPUT Serial #endif const char * AUTHORIZATION_HEADER = "Authorization"; ESP8266WebServer::ESP8266WebServer(IPAddress addr, int port) : _server(addr, port) , _currentMethod(HTTP_ANY) , _currentVersion(0) , _currentStatus(HC_NONE) , _statusChange(0) , _currentHandler(0) , _firstHandler(0) , _lastHandler(0) , _currentArgCount(0) , _currentArgs(0) , _headerKeysCount(0) , _currentHeaders(0) , _contentLength(0) , _chunked(false) { } ESP8266WebServer::ESP8266WebServer(int port) : _server(port) , _currentMethod(HTTP_ANY) , _currentVersion(0) , _currentStatus(HC_NONE) , _statusChange(0) , _currentHandler(0) , _firstHandler(0) , _lastHandler(0) , _currentArgCount(0) , _currentArgs(0) , _headerKeysCount(0) , _currentHeaders(0) , _contentLength(0) , _chunked(false) { } ESP8266WebServer::~ESP8266WebServer() { if (_currentHeaders) delete[]_currentHeaders; _headerKeysCount = 0; RequestHandler* handler = _firstHandler; while (handler) { RequestHandler* next = handler->next(); delete handler; handler = next; } close(); } void ESP8266WebServer::begin() { _currentStatus = HC_NONE; _server.begin(); if(!_headerKeysCount) collectHeaders(0, 0); } bool ESP8266WebServer::authenticate(const char * username, const char * password){ if(hasHeader(AUTHORIZATION_HEADER)){ String authReq = header(AUTHORIZATION_HEADER); if(authReq.startsWith("Basic")){ authReq = authReq.substring(6); authReq.trim(); char toencodeLen = strlen(username)+strlen(password)+1; char *toencode = new char[toencodeLen + 1]; if(toencode == NULL){ authReq = String(); return false; } char *encoded = new char[base64_encode_expected_len(toencodeLen)+1]; if(encoded == NULL){ authReq = String(); delete[] toencode; return false; } sprintf(toencode, "%s:%s", username, password); if(base64_encode_chars(toencode, toencodeLen, encoded) > 0 && authReq.equals(encoded)){ authReq = String(); delete[] toencode; delete[] encoded; return true; } delete[] toencode; delete[] encoded; } authReq = String(); } return false; } void ESP8266WebServer::requestAuthentication(){ sendHeader("WWW-Authenticate", "Basic realm=\"Login Required\""); send(401); } void ESP8266WebServer::on(const String &uri, ESP8266WebServer::THandlerFunction handler) { on(uri, HTTP_ANY, handler); } void ESP8266WebServer::on(const String &uri, HTTPMethod method, ESP8266WebServer::THandlerFunction fn) { on(uri, method, fn, _fileUploadHandler); } void ESP8266WebServer::on(const String &uri, HTTPMethod method, ESP8266WebServer::THandlerFunction fn, ESP8266WebServer::THandlerFunction ufn) { _addRequestHandler(new FunctionRequestHandler(fn, ufn, uri, method)); } void ESP8266WebServer::addHandler(RequestHandler* handler) { _addRequestHandler(handler); } void ESP8266WebServer::_addRequestHandler(RequestHandler* handler) { if (!_lastHandler) { _firstHandler = handler; _lastHandler = handler; } else { _lastHandler->next(handler); _lastHandler = handler; } } void ESP8266WebServer::serveStatic(const char* uri, FS& fs, const char* path, const char* cache_header) { _addRequestHandler(new StaticRequestHandler(fs, path, uri, cache_header)); } void ESP8266WebServer::handleClient() { if (_currentStatus == HC_NONE) { WiFiClient client = _server.available(); if (!client) { return; } #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.println("New client"); #endif _currentClient = client; _currentStatus = HC_WAIT_READ; _statusChange = millis(); } if (!_currentClient.connected()) { _currentClient = WiFiClient(); _currentStatus = HC_NONE; return; } // Wait for data from client to become available if (_currentStatus == HC_WAIT_READ) { if (!_currentClient.available()) { if (millis() - _statusChange > HTTP_MAX_DATA_WAIT) { _currentClient = WiFiClient(); _currentStatus = HC_NONE; } yield(); return; } if (!_parseRequest(_currentClient)) { _currentClient = WiFiClient(); _currentStatus = HC_NONE; return; } _currentClient.setTimeout(HTTP_MAX_SEND_WAIT); _contentLength = CONTENT_LENGTH_NOT_SET; _handleRequest(); if (!_currentClient.connected()) { _currentClient = WiFiClient(); _currentStatus = HC_NONE; return; } else { _currentStatus = HC_WAIT_CLOSE; _statusChange = millis(); return; } } if (_currentStatus == HC_WAIT_CLOSE) { if (millis() - _statusChange > HTTP_MAX_CLOSE_WAIT) { _currentClient = WiFiClient(); _currentStatus = HC_NONE; } else { yield(); return; } } } void ESP8266WebServer::close() { _server.close(); } void ESP8266WebServer::stop() { close(); } void ESP8266WebServer::sendHeader(const String& name, const String& value, bool first) { String headerLine = name; headerLine += ": "; headerLine += value; headerLine += "\r\n"; if (first) { _responseHeaders = headerLine + _responseHeaders; } else { _responseHeaders += headerLine; } } void ESP8266WebServer::setContentLength(size_t contentLength) { _contentLength = contentLength; } void ESP8266WebServer::_prepareHeader(String& response, int code, const char* content_type, size_t contentLength) { response = "HTTP/1."+String(_currentVersion)+" "; response += String(code); response += " "; response += _responseCodeToString(code); response += "\r\n"; if (!content_type) content_type = "text/html"; sendHeader("Content-Type", content_type, true); if (_contentLength == CONTENT_LENGTH_NOT_SET) { sendHeader("Content-Length", String(contentLength)); } else if (_contentLength != CONTENT_LENGTH_UNKNOWN) { sendHeader("Content-Length", String(_contentLength)); } else if(_contentLength == CONTENT_LENGTH_UNKNOWN && _currentVersion){ //HTTP/1.1 or above client //let's do chunked _chunked = true; sendHeader("Accept-Ranges","none"); sendHeader("Transfer-Encoding","chunked"); } sendHeader("Connection", "close"); response += _responseHeaders; response += "\r\n"; _responseHeaders = String(); } void ESP8266WebServer::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()); _currentClient.write(header.c_str(), header.length()); if(content.length()) sendContent(content); } void ESP8266WebServer::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); _currentClient.write(header.c_str(), header.length()); sendContent_P(content); } void ESP8266WebServer::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 ESP8266WebServer::send(int code, char* content_type, const String& content) { send(code, (const char*)content_type, content); } void ESP8266WebServer::send(int code, const String& content_type, const String& content) { send(code, (const char*)content_type.c_str(), content); } void ESP8266WebServer::sendContent(const String& content) { const char * footer = "\r\n"; size_t len = content.length(); if(_chunked) { char * chunkSize = (char *)malloc(11); if(chunkSize){ sprintf(chunkSize, "%x%s", len, footer); _currentClient.write(chunkSize, strlen(chunkSize)); free(chunkSize); } } _currentClient.write(content.c_str(), len); if(_chunked){ _currentClient.write(footer, 2); } } void ESP8266WebServer::sendContent_P(PGM_P content) { sendContent_P(content, strlen_P(content)); } void ESP8266WebServer::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); _currentClient.write(chunkSize, strlen(chunkSize)); free(chunkSize); } } _currentClient.write_P(content, size); if(_chunked){ _currentClient.write(footer, 2); } } String ESP8266WebServer::arg(String name) { for (int i = 0; i < _currentArgCount; ++i) { if ( _currentArgs[i].key == name ) return _currentArgs[i].value; } return String(); } String ESP8266WebServer::arg(int i) { if (i < _currentArgCount) return _currentArgs[i].value; return String(); } String ESP8266WebServer::argName(int i) { if (i < _currentArgCount) return _currentArgs[i].key; return String(); } int ESP8266WebServer::args() { return _currentArgCount; } bool ESP8266WebServer::hasArg(String name) { for (int i = 0; i < _currentArgCount; ++i) { if (_currentArgs[i].key == name) return true; } return false; } String ESP8266WebServer::header(String name) { for (int i = 0; i < _headerKeysCount; ++i) { if (_currentHeaders[i].key.equalsIgnoreCase(name)) return _currentHeaders[i].value; } return String(); } void ESP8266WebServer::collectHeaders(const char* headerKeys[], const size_t headerKeysCount) { _headerKeysCount = headerKeysCount + 1; if (_currentHeaders) delete[]_currentHeaders; _currentHeaders = new RequestArgument[_headerKeysCount]; _currentHeaders[0].key = AUTHORIZATION_HEADER; for (int i = 1; i < _headerKeysCount; i++){ _currentHeaders[i].key = headerKeys[i-1]; } } String ESP8266WebServer::header(int i) { if (i < _headerKeysCount) return _currentHeaders[i].value; return String(); } String ESP8266WebServer::headerName(int i) { if (i < _headerKeysCount) return _currentHeaders[i].key; return String(); } int ESP8266WebServer::headers() { return _headerKeysCount; } bool ESP8266WebServer::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 ESP8266WebServer::hostHeader() { return _hostHeader; } void ESP8266WebServer::onFileUpload(THandlerFunction fn) { _fileUploadHandler = fn; } void ESP8266WebServer::onNotFound(THandlerFunction fn) { _notFoundHandler = fn; } void ESP8266WebServer::_handleRequest() { bool handled = false; if (!_currentHandler){ #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.println("request handler not found"); #endif } else { handled = _currentHandler->handle(*this, _currentMethod, _currentUri); #ifdef DEBUG_ESP_HTTP_SERVER if (!handled) { DEBUG_OUTPUT.println("request handler failed to handle request"); } #endif } if (!handled) { if(_notFoundHandler) { _notFoundHandler(); } else { send(404, "text/plain", String("Not found: ") + _currentUri); } } _currentUri = String(); } String ESP8266WebServer::_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 ""; } } ================================================ FILE: src/ESP8266WebServer.h ================================================ /* ESP8266WebServer.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 ESP8266WEBSERVER_H #define ESP8266WEBSERVER_H #include #include enum HTTPMethod { HTTP_ANY, HTTP_GET, HTTP_POST, HTTP_PUT, HTTP_PATCH, HTTP_DELETE, HTTP_OPTIONS }; enum HTTPUploadStatus { UPLOAD_FILE_START, UPLOAD_FILE_WRITE, UPLOAD_FILE_END, UPLOAD_FILE_ABORTED }; enum HTTPClientStatus { HC_NONE, HC_WAIT_READ, HC_WAIT_CLOSE }; #define HTTP_DOWNLOAD_UNIT_SIZE 1460 #ifndef HTTP_UPLOAD_BUFLEN #define HTTP_UPLOAD_BUFLEN 2048 #endif #define HTTP_MAX_DATA_WAIT 1000 //ms to wait for the client to send the request #define HTTP_MAX_POST_WAIT 1000 //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 ESP8266WebServer; 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" namespace fs { class FS; } class ESP8266WebServer { public: ESP8266WebServer(IPAddress addr, int port = 80); ESP8266WebServer(int port = 80); ~ESP8266WebServer(); void begin(); void handleClient(); void close(); void stop(); bool authenticate(const char * username, const char * password); void requestAuthentication(); typedef std::function THandlerFunction; void on(const String &uri, THandlerFunction handler); void on(const String &uri, HTTPMethod method, THandlerFunction fn); void on(const String &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; } WiFiClient client() { return _currentClient; } HTTPUpload& upload() { return _currentUpload; } 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 setContentLength(size_t contentLength); void sendHeader(const String& name, const String& value, bool first = false); void sendContent(const String& content); void sendContent_P(PGM_P content); void sendContent_P(PGM_P content, size_t size); static String urlDecode(const String& text); template size_t streamFile(T &file, const String& contentType){ setContentLength(file.size()); if (String(file.name()).endsWith(".gz") && contentType != "application/x-gzip" && contentType != "application/octet-stream"){ sendHeader("Content-Encoding", "gzip"); } send(200, contentType, ""); return _currentClient.write(file); } protected: void _addRequestHandler(RequestHandler* handler); void _handleRequest(); bool _parseRequest(WiFiClient& client); void _parseArguments(String data); static String _responseCodeToString(int code); bool _parseForm(WiFiClient& client, String boundary, uint32_t len); bool _parseFormUploadAborted(); void _uploadWriteByte(uint8_t b); uint8_t _uploadReadByte(WiFiClient& client); void _prepareHeader(String& response, int code, const char* content_type, size_t contentLength); bool _collectHeader(const char* headerName, const char* headerValue); struct RequestArgument { String key; String value; }; WiFiServer _server; WiFiClient _currentClient; HTTPMethod _currentMethod; String _currentUri; uint8_t _currentVersion; HTTPClientStatus _currentStatus; unsigned long _statusChange; RequestHandler* _currentHandler; RequestHandler* _firstHandler; RequestHandler* _lastHandler; THandlerFunction _notFoundHandler; THandlerFunction _fileUploadHandler; int _currentArgCount; RequestArgument* _currentArgs; HTTPUpload _currentUpload; int _headerKeysCount; RequestArgument* _currentHeaders; size_t _contentLength; String _responseHeaders; String _hostHeader; bool _chunked; }; #endif //ESP8266WEBSERVER_H ================================================ FILE: src/Parsing.cpp ================================================ /* 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 #include "WiFiServer.h" #include "WiFiClient.h" #include "ESP8266WebServer.h" //#define DEBUG_ESP_HTTP_SERVER #ifdef DEBUG_ESP_PORT #define DEBUG_OUTPUT DEBUG_ESP_PORT #else #define DEBUG_OUTPUT Serial #endif static char* readBytesWithTimeout(WiFiClient& 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 ESP8266WebServer::_parseRequest(WiFiClient& 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) { #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.print("Invalid request: "); DEBUG_OUTPUT.println(req); #endif 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 = urlDecode(url.substring(hasSearch + 1)); url = url.substring(0, hasSearch); } _currentUri = url; _chunked = false; HTTPMethod method = HTTP_GET; if (methodStr == "POST") { method = HTTP_POST; } else if (methodStr == "DELETE") { method = HTTP_DELETE; } else if (methodStr == "OPTIONS") { method = HTTP_OPTIONS; } else if (methodStr == "PUT") { method = HTTP_PUT; } else if (methodStr == "PATCH") { method = HTTP_PATCH; } _currentMethod = method; #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.print("method: "); DEBUG_OUTPUT.print(methodStr); DEBUG_OUTPUT.print(" url: "); DEBUG_OUTPUT.print(url); DEBUG_OUTPUT.print(" search: "); DEBUG_OUTPUT.println(searchStr); #endif //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()); #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.print("headerName: "); DEBUG_OUTPUT.println(headerName); DEBUG_OUTPUT.print("headerValue: "); DEBUG_OUTPUT.println(headerValue); #endif if (headerName.equalsIgnoreCase("Content-Type")){ if (headerValue.startsWith("text/plain")){ isForm = false; } else if (headerValue.startsWith("application/x-www-form-urlencoded")){ isForm = false; isEncoded = true; } else if (headerValue.startsWith("multipart/")){ boundaryStr = headerValue.substring(headerValue.indexOf('=')+1); isForm = true; } } else if (headerName.equalsIgnoreCase("Content-Length")){ contentLength = headerValue.toInt(); } else if (headerName.equalsIgnoreCase("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 (searchStr != "") searchStr += '&'; if(isEncoded){ //url encoded form String decoded = urlDecode(plainBuf); size_t decodedLen = decoded.length(); memcpy(plainBuf, decoded.c_str(), decodedLen); plainBuf[decodedLen] = 0; searchStr += plainBuf; } _parseArguments(searchStr); if(!isEncoded){ //plain post json or other data RequestArgument& arg = _currentArgs[_currentArgCount++]; arg.key = "plain"; arg.value = String(plainBuf); } #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.print("Plain: "); DEBUG_OUTPUT.println(plainBuf); #endif free(plainBuf); } } 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()); #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.print("headerName: "); DEBUG_OUTPUT.println(headerName); DEBUG_OUTPUT.print("headerValue: "); DEBUG_OUTPUT.println(headerValue); #endif if (headerName.equalsIgnoreCase("Host")){ _hostHeader = headerValue; } } _parseArguments(searchStr); } client.flush(); #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.print("Request: "); DEBUG_OUTPUT.println(url); DEBUG_OUTPUT.print(" Arguments: "); DEBUG_OUTPUT.println(searchStr); #endif return true; } bool ESP8266WebServer::_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 ESP8266WebServer::_parseArguments(String data) { #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.print("args: "); DEBUG_OUTPUT.println(data); #endif 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; } #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.print("args count: "); DEBUG_OUTPUT.println(_currentArgCount); #endif _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); #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.print("pos "); DEBUG_OUTPUT.print(pos); DEBUG_OUTPUT.print("=@ "); DEBUG_OUTPUT.print(equal_sign_index); DEBUG_OUTPUT.print(" &@ "); DEBUG_OUTPUT.println(next_arg_index); #endif if ((equal_sign_index == -1) || ((equal_sign_index > next_arg_index) && (next_arg_index != -1))) { #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.print("arg missing value: "); DEBUG_OUTPUT.println(iarg); #endif if (next_arg_index == -1) break; pos = next_arg_index + 1; continue; } RequestArgument& arg = _currentArgs[iarg]; arg.key = data.substring(pos, equal_sign_index); arg.value = data.substring(equal_sign_index + 1, next_arg_index); #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.print("arg "); DEBUG_OUTPUT.print(iarg); DEBUG_OUTPUT.print(" key: "); DEBUG_OUTPUT.print(arg.key); DEBUG_OUTPUT.print(" value: "); DEBUG_OUTPUT.println(arg.value); #endif ++iarg; if (next_arg_index == -1) break; pos = next_arg_index + 1; } _currentArgCount = iarg; #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.print("args count: "); DEBUG_OUTPUT.println(_currentArgCount); #endif } void ESP8266WebServer::_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; } uint8_t ESP8266WebServer::_uploadReadByte(WiFiClient& client){ int res = client.read(); if(res == -1){ while(!client.available() && client.connected()) yield(); res = client.read(); } return (uint8_t)res; } bool ESP8266WebServer::_parseForm(WiFiClient& client, String boundary, uint32_t len){ (void) len; #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.print("Parse Form: Boundary: "); DEBUG_OUTPUT.print(boundary); DEBUG_OUTPUT.print(" Length: "); DEBUG_OUTPUT.println(len); #endif 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)){ RequestArgument* postArgs = new RequestArgument[32]; int 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("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; #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.print("PostArg FileName: "); DEBUG_OUTPUT.println(argFilename); #endif //use GET to set the filename if uploading using blob if (argFilename == "blob" && hasArg("filename")) argFilename = arg("filename"); } #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.print("PostArg Name: "); DEBUG_OUTPUT.println(argName); #endif argType = "text/plain"; line = client.readStringUntil('\r'); client.readStringUntil('\n'); if (line.length() > 12 && line.substring(0, 12).equalsIgnoreCase("Content-Type")){ argType = line.substring(line.indexOf(':')+2); //skip next line client.readStringUntil('\r'); client.readStringUntil('\n'); } #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.print("PostArg Type: "); DEBUG_OUTPUT.println(argType); #endif if (!argIsFile){ while(1){ line = client.readStringUntil('\r'); client.readStringUntil('\n'); if (line.startsWith("--"+boundary)) break; if (argValue.length() > 0) argValue += "\n"; argValue += line; } #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.print("PostArg Value: "); DEBUG_OUTPUT.println(argValue); DEBUG_OUTPUT.println(); #endif RequestArgument& arg = postArgs[postArgsLen++]; arg.key = argName; arg.value = argValue; if (line == ("--"+boundary+"--")){ #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.println("Done Parsing POST"); #endif break; } } else { _currentUpload.status = UPLOAD_FILE_START; _currentUpload.name = argName; _currentUpload.filename = argFilename; _currentUpload.type = argType; _currentUpload.totalSize = 0; _currentUpload.currentSize = 0; #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.print("Start File: "); DEBUG_OUTPUT.print(_currentUpload.filename); DEBUG_OUTPUT.print(" Type: "); DEBUG_OUTPUT.println(_currentUpload.type); #endif if(_currentHandler && _currentHandler->canUpload(_currentUri)) _currentHandler->upload(*this, _currentUri, _currentUpload); _currentUpload.status = UPLOAD_FILE_WRITE; uint8_t argByte = _uploadReadByte(client); readfile: while(argByte != 0x0D){ if (!client.connected()) return _parseFormUploadAborted(); _uploadWriteByte(argByte); argByte = _uploadReadByte(client); } argByte = _uploadReadByte(client); if (!client.connected()) return _parseFormUploadAborted(); if (argByte == 0x0A){ argByte = _uploadReadByte(client); if (!client.connected()) return _parseFormUploadAborted(); if ((char)argByte != '-'){ //continue reading the file _uploadWriteByte(0x0D); _uploadWriteByte(0x0A); goto readfile; } else { argByte = _uploadReadByte(client); if (!client.connected()) return _parseFormUploadAborted(); if ((char)argByte != '-'){ //continue reading the file _uploadWriteByte(0x0D); _uploadWriteByte(0x0A); _uploadWriteByte((uint8_t)('-')); goto readfile; } } uint8_t endBuf[boundary.length()]; client.readBytes(endBuf, boundary.length()); 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); #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.print("End File: "); DEBUG_OUTPUT.print(_currentUpload.filename); DEBUG_OUTPUT.print(" Type: "); DEBUG_OUTPUT.print(_currentUpload.type); DEBUG_OUTPUT.print(" Size: "); DEBUG_OUTPUT.println(_currentUpload.totalSize); #endif line = client.readStringUntil(0x0D); client.readStringUntil(0x0A); if (line == "--"){ #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.println("Done Parsing POST"); #endif 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 = ((32 - postArgsLen) < _currentArgCount)?(32 - 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; return true; } #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.print("Error: line: "); DEBUG_OUTPUT.println(line); #endif return false; } String ESP8266WebServer::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 ESP8266WebServer::_parseFormUploadAborted(){ _currentUpload.status = UPLOAD_FILE_ABORTED; if(_currentHandler && _currentHandler->canUpload(_currentUri)) _currentHandler->upload(*this, _currentUri, _currentUpload); return false; } ================================================ FILE: src/detail/RequestHandler.h ================================================ #ifndef REQUESTHANDLER_H #define REQUESTHANDLER_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(ESP8266WebServer& server, HTTPMethod requestMethod, String requestUri) { (void) server; (void) requestMethod; (void) requestUri; return false; } virtual void upload(ESP8266WebServer& 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; }; #endif //REQUESTHANDLER_H ================================================ FILE: src/detail/RequestHandlersImpl.h ================================================ #ifndef REQUESTHANDLERSIMPL_H #define REQUESTHANDLERSIMPL_H #include "RequestHandler.h" class FunctionRequestHandler : public RequestHandler { public: FunctionRequestHandler(ESP8266WebServer::THandlerFunction fn, ESP8266WebServer::THandlerFunction ufn, const String &uri, HTTPMethod method) : _fn(fn) , _ufn(ufn) , _uri(uri) , _method(method) { } bool canHandle(HTTPMethod requestMethod, String requestUri) override { if (_method != HTTP_ANY && _method != requestMethod) return false; if (requestUri != _uri) return false; return true; } bool canUpload(String requestUri) override { if (!_ufn || !canHandle(HTTP_POST, requestUri)) return false; return true; } bool handle(ESP8266WebServer& server, HTTPMethod requestMethod, String requestUri) override { (void) server; if (!canHandle(requestMethod, requestUri)) return false; _fn(); return true; } void upload(ESP8266WebServer& server, String requestUri, HTTPUpload& upload) override { (void) server; (void) upload; if (canUpload(requestUri)) _ufn(); } protected: ESP8266WebServer::THandlerFunction _fn; ESP8266WebServer::THandlerFunction _ufn; String _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); DEBUGV("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(ESP8266WebServer& server, HTTPMethod requestMethod, String requestUri) override { if (!canHandle(requestMethod, requestUri)) return false; DEBUGV("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); } DEBUGV("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(".gz") && !_fs.exists(path)) { String pathWithGz = path + ".gz"; if(_fs.exists(pathWithGz)) path += ".gz"; } File f = _fs.open(path, "r"); if (!f) 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) { if (path.endsWith(".html")) return "text/html"; else if (path.endsWith(".htm")) return "text/html"; else if (path.endsWith(".css")) return "text/css"; else if (path.endsWith(".txt")) return "text/plain"; else if (path.endsWith(".js")) return "application/javascript"; else if (path.endsWith(".json")) return "application/json"; else if (path.endsWith(".png")) return "image/png"; else if (path.endsWith(".gif")) return "image/gif"; else if (path.endsWith(".jpg")) return "image/jpeg"; else if (path.endsWith(".ico")) return "image/x-icon"; else if (path.endsWith(".svg")) return "image/svg+xml"; else if (path.endsWith(".ttf")) return "application/x-font-ttf"; else if (path.endsWith(".otf")) return "application/x-font-opentype"; else if (path.endsWith(".woff")) return "application/font-woff"; else if (path.endsWith(".woff2")) return "application/font-woff2"; else if (path.endsWith(".eot")) return "application/vnd.ms-fontobject"; else if (path.endsWith(".sfnt")) return "application/font-sfnt"; else if (path.endsWith(".xml")) return "text/xml"; else if (path.endsWith(".pdf")) return "application/pdf"; else if (path.endsWith(".zip")) return "application/zip"; else if(path.endsWith(".gz")) return "application/x-gzip"; else if (path.endsWith(".appcache")) return "text/cache-manifest"; return "application/octet-stream"; } protected: FS _fs; String _uri; String _path; String _cache_header; bool _isFile; size_t _baseUriLength; }; #endif //REQUESTHANDLERSIMPL_H