| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
10 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -14,6 +14,7 @@ jobs: | |||
| 14 | 14 | example: | |
| 15 | 15 | - Async-Server | |
| 16 | 16 | - Authentication | |
| 17 | + - HTML-Forms | ||
| 17 | 18 | - HTTPS-and-HTTP | |
| 18 | 19 | - Middleware | |
| 19 | 20 | - Parameters | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -66,4 +66,7 @@ cert.h | |||
| 66 | 66 | private_key.h | |
| 67 | 67 | ||
| 68 | 68 | # Do not push VS Code project files | |
| 69 | - .vscode | ||
| 69 | + .vscode | ||
| 70 | + | ||
| 71 | + # Ignore platformio work directory | ||
| 72 | + .pio | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,377 @@ | |||
| 1 | + /** | ||
| 2 | + * Example for the ESP32 HTTP(S) Webserver | ||
| 3 | + * | ||
| 4 | + * IMPORTANT NOTE: | ||
| 5 | + * To run this script, you need to | ||
| 6 | + * 1) Enter your WiFi SSID and PSK below this comment | ||
| 7 | + * 2) Make sure to have certificate data available. You will find a | ||
| 8 | + * shell script and instructions to do so in the library folder | ||
| 9 | + * under extras/ | ||
| 10 | + * | ||
| 11 | + * This script will install an HTTPS Server on your ESP32 with the following | ||
| 12 | + * functionalities: | ||
| 13 | + * - Show simple page on web server root that includes some HTML Forms | ||
| 14 | + * - Define a POST handler that handles the forms using the HTTPBodyParser API | ||
| 15 | + * provided by the library. | ||
| 16 | + * - 404 for everything else | ||
| 17 | + */ | ||
| 18 | + | ||
| 19 | + // TODO: Configure your WiFi here | ||
| 20 | + #define WIFI_SSID "<your ssid goes here>" | ||
| 21 | + #define WIFI_PSK "<your pre-shared key goes here>" | ||
| 22 | + | ||
| 23 | + // Include certificate data (see note above) | ||
| 24 | + #include "cert.h" | ||
| 25 | + #include "private_key.h" | ||
| 26 | + | ||
| 27 | + // We will use wifi | ||
| 28 | + #include <WiFi.h> | ||
| 29 | + | ||
| 30 | + // We will use SPIFFS and FS | ||
| 31 | + #include <SPIFFS.h> | ||
| 32 | + #include <FS.h> | ||
| 33 | + | ||
| 34 | + // Includes for the server | ||
| 35 | + #include <HTTPSServer.hpp> | ||
| 36 | + #include <SSLCert.hpp> | ||
| 37 | + #include <HTTPRequest.hpp> | ||
| 38 | + #include <HTTPResponse.hpp> | ||
| 39 | + #include <HTTPBodyParser.hpp> | ||
| 40 | + #include <HTTPMultipartBodyParser.hpp> | ||
| 41 | + #include <HTTPURLEncodedBodyParser.hpp> | ||
| 42 | + | ||
| 43 | + // We need to specify some content-type mapping, so the resources get delivered with the | ||
| 44 | + // right content type and are displayed correctly in the browser | ||
| 45 | + char contentTypes[][2][32] = { | ||
| 46 | + {".txt", "text/plain"}, | ||
| 47 | + {".png", "image/png"}, | ||
| 48 | + {".jpg", "image/jpg"}, | ||
| 49 | + {"", ""} | ||
| 50 | + }; | ||
| 51 | + | ||
| 52 | + // The HTTPS Server comes in a separate namespace. For easier use, include it here. | ||
| 53 | + using namespace httpsserver; | ||
| 54 | + | ||
| 55 | + // Create an SSL certificate object from the files included above | ||
| 56 | + SSLCert cert = SSLCert( | ||
| 57 | + example_crt_DER, example_crt_DER_len, | ||
| 58 | + example_key_DER, example_key_DER_len | ||
| 59 | + ); | ||
| 60 | + | ||
| 61 | + // Create an SSL-enabled server that uses the certificate | ||
| 62 | + // The contstructor takes some more parameters, but we go for default values here. | ||
| 63 | + HTTPSServer secureServer = HTTPSServer(&cert); | ||
| 64 | + | ||
| 65 | + // Declare some handler functions for the various URLs on the server | ||
| 66 | + // The signature is always the same for those functions. They get two parameters, | ||
| 67 | + // which are pointers to the request data (read request body, headers, ...) and | ||
| 68 | + // to the response data (write response, set status code, ...) | ||
| 69 | + void handleRoot(HTTPRequest * req, HTTPResponse * res); | ||
| 70 | + void handleFormUpload(HTTPRequest * req, HTTPResponse * res); | ||
| 71 | + void handleFormEdit(HTTPRequest * req, HTTPResponse * res); | ||
| 72 | + void handleFile(HTTPRequest * req, HTTPResponse * res); | ||
| 73 | + void handleDirectory(HTTPRequest * req, HTTPResponse * res); | ||
| 74 | + void handle404(HTTPRequest * req, HTTPResponse * res); | ||
| 75 | + | ||
| 76 | + std::string htmlEncode(std::string data) { | ||
| 77 | + // Quick and dirty: doesn't handle control chars and such. | ||
| 78 | + const char *p = data.c_str(); | ||
| 79 | + std::string rv = ""; | ||
| 80 | + while(p && *p) { | ||
| 81 | + char escapeChar = *p++; | ||
| 82 | + switch(escapeChar) { | ||
| 83 | + case '&': rv += "&"; break; | ||
| 84 | + case '<': rv += "<"; break; | ||
| 85 | + case '>': rv += ">"; break; | ||
| 86 | + case '"': rv += """; break; | ||
| 87 | + case '\'': rv += "'"; break; | ||
| 88 | + case '/': rv += "/"; break; | ||
| 89 | + default: rv += escapeChar; break; | ||
| 90 | + } | ||
| 91 | + } | ||
| 92 | + return rv; | ||
| 93 | + } | ||
| 94 | + | ||
| 95 | + void setup() { | ||
| 96 | + // For logging | ||
| 97 | + Serial.begin(115200); | ||
| 98 | + // Connect to WiFi | ||
| 99 | + Serial.println("Setting up WiFi"); | ||
| 100 | + WiFi.begin(WIFI_SSID, WIFI_PSK); | ||
| 101 | + while (WiFi.status() != WL_CONNECTED) { | ||
| 102 | + Serial.print("."); | ||
| 103 | + delay(500); | ||
| 104 | + } | ||
| 105 | + Serial.print("Connected. IP="); | ||
| 106 | + Serial.println(WiFi.localIP()); | ||
| 107 | + | ||
| 108 | + // Setup filesystem | ||
| 109 | + if (!SPIFFS.begin(true)) Serial.println("Mounting SPIFFS failed"); | ||
| 110 | + | ||
| 111 | + // For every resource available on the server, we need to create a ResourceNode | ||
| 112 | + // The ResourceNode links URL and HTTP method to a handler function | ||
| 113 | + ResourceNode * nodeRoot = new ResourceNode("/", "GET", &handleRoot); | ||
| 114 | + ResourceNode * nodeFormUpload = new ResourceNode("/upload", "POST", &handleFormUpload); | ||
| 115 | + ResourceNode * nodeFormEdit = new ResourceNode("/edit", "GET", &handleFormEdit); | ||
| 116 | + ResourceNode * nodeFormEditDone = new ResourceNode("/edit", "POST", &handleFormEdit); | ||
| 117 | + ResourceNode * nodeDirectory = new ResourceNode("/public", "GET", &handleDirectory); | ||
| 118 | + ResourceNode * nodeFile = new ResourceNode("/public/*", "GET", &handleFile); | ||
| 119 | + | ||
| 120 | + // 404 node has no URL as it is used for all requests that don't match anything else | ||
| 121 | + ResourceNode * node404 = new ResourceNode("", "GET", &handle404); | ||
| 122 | + | ||
| 123 | + // Add the root nodes to the server | ||
| 124 | + secureServer.registerNode(nodeRoot); | ||
| 125 | + secureServer.registerNode(nodeFormUpload); | ||
| 126 | + secureServer.registerNode(nodeFormEdit); | ||
| 127 | + secureServer.registerNode(nodeFormEditDone); | ||
| 128 | + secureServer.registerNode(nodeDirectory); | ||
| 129 | + secureServer.registerNode(nodeFile); | ||
| 130 | + | ||
| 131 | + // Add the 404 not found node to the server. | ||
| 132 | + // The path is ignored for the default node. | ||
| 133 | + secureServer.setDefaultNode(node404); | ||
| 134 | + | ||
| 135 | + Serial.println("Starting server..."); | ||
| 136 | + secureServer.start(); | ||
| 137 | + if (secureServer.isRunning()) { | ||
| 138 | + Serial.println("Server ready."); | ||
| 139 | + } | ||
| 140 | + } | ||
| 141 | + | ||
| 142 | + void loop() { | ||
| 143 | + // This call will let the server do its work | ||
| 144 | + secureServer.loop(); | ||
| 145 | + | ||
| 146 | + // Other code would go here... | ||
| 147 | + delay(1); | ||
| 148 | + } | ||
| 149 | + | ||
| 150 | + void handleRoot(HTTPRequest * req, HTTPResponse * res) { | ||
| 151 | + // Status code is 200 OK by default. | ||
| 152 | + // We want to deliver a simple HTML page, so we send a corresponding content type: | ||
| 153 | + res->setHeader("Content-Type", "text/html"); | ||
| 154 | + | ||
| 155 | + // The response implements the Print interface, so you can use it just like | ||
| 156 | + // you would write to Serial etc. | ||
| 157 | + res->println("<!DOCTYPE html>"); | ||
| 158 | + res->println("<html>"); | ||
| 159 | + res->println("<head><title>Very simple file server</title></head>"); | ||
| 160 | + res->println("<body>"); | ||
| 161 | + res->println("<h1>Very simple file server</h1>"); | ||
| 162 | + res->println("<p>This is a very simple file server to demonstrate the use of POST forms. </p>"); | ||
| 163 | + res->println("<h2>List existing files</h2>"); | ||
| 164 | + res->println("<p>See <a href=\"/public\">/public</a> to list existing files and retrieve or edit them.</p>"); | ||
| 165 | + res->println("<h2>Upload new file</h2>"); | ||
| 166 | + res->println("<p>This form allows you to upload files (text, jpg and png supported best). It demonstrates multipart/form-data.</p>"); | ||
| 167 | + res->println("<form method=\"POST\" action=\"/upload\" enctype=\"multipart/form-data\">"); | ||
| 168 | + res->println("file: <input type=\"file\" name=\"file\"><br>"); | ||
| 169 | + res->println("<input type=\"submit\" value=\"Upload\">"); | ||
| 170 | + res->println("</form>"); | ||
| 171 | + res->println("</body>"); | ||
| 172 | + res->println("</html>"); | ||
| 173 | + } | ||
| 174 | + | ||
| 175 | + void handleFormUpload(HTTPRequest * req, HTTPResponse * res) { | ||
| 176 | + // First, we need to check the encoding of the form that we have received. | ||
| 177 | + // The browser will set the Content-Type request header, so we can use it for that purpose. | ||
| 178 | + // Then we select the body parser based on the encoding. | ||
| 179 | + // Actually we do this only for documentary purposes, we know the form is going | ||
| 180 | + // to be multipart/form-data. | ||
| 181 | + HTTPBodyParser *parser; | ||
| 182 | + std::string contentType = req->getHeader("Content-Type"); | ||
| 183 | + size_t semicolonPos = contentType.find(";"); | ||
| 184 | + if (semicolonPos != std::string::npos) { | ||
| 185 | + contentType = contentType.substr(0, semicolonPos); | ||
| 186 | + } | ||
| 187 | + if (contentType == "multipart/form-data") { | ||
| 188 | + parser = new HTTPMultipartBodyParser(req); | ||
| 189 | + } else { | ||
| 190 | + Serial.printf("Unknown POST Content-Type: %s\n", contentType.c_str()); | ||
| 191 | + return; | ||
| 192 | + } | ||
| 193 | + // We iterate over the fields. Any field with a filename is uploaded | ||
| 194 | + res->println("<html><head><title>File Upload</title></head><body><h1>File Upload</h1>"); | ||
| 195 | + bool didwrite = false; | ||
| 196 | + while(parser->nextField()) { | ||
| 197 | + std::string name = parser->getFieldName(); | ||
| 198 | + std::string filename = parser->getFieldFilename(); | ||
| 199 | + std::string mimeType = parser->getFieldMimeType(); | ||
| 200 | + Serial.printf("handleFormUpload: field name='%s', filename='%s', mimetype='%s'\n", name.c_str(), filename.c_str(), mimeType.c_str()); | ||
| 201 | + // Double check that it is what we expect | ||
| 202 | + if (name != "file") { | ||
| 203 | + Serial.println("Skipping unexpected field"); | ||
| 204 | + break; | ||
| 205 | + } | ||
| 206 | + // Should check file name validity and all that, but we skip that. | ||
| 207 | + std::string pathname = "/public/" + filename; | ||
| 208 | + File file = SPIFFS.open(pathname.c_str(), "w"); | ||
| 209 | + size_t fileLength = 0; | ||
| 210 | + didwrite = true; | ||
| 211 | + while (!parser->endOfField()) { | ||
| 212 | + byte buf[512]; | ||
| 213 | + size_t readLength = parser->read(buf, 512); | ||
| 214 | + file.write(buf, readLength); | ||
| 215 | + fileLength += readLength; | ||
| 216 | + } | ||
| 217 | + file.close(); | ||
| 218 | + res->printf("<p>Saved %d bytes to %s</p>", (int)fileLength, pathname.c_str()); | ||
| 219 | + } | ||
| 220 | + if (!didwrite) { | ||
| 221 | + res->println("<p>Did not write any file</p>"); | ||
| 222 | + } | ||
| 223 | + res->println("</body></html>"); | ||
| 224 | + delete parser; | ||
| 225 | + } | ||
| 226 | + | ||
| 227 | + void handleFormEdit(HTTPRequest * req, HTTPResponse * res) { | ||
| 228 | + if (req->getMethod() == "GET") { | ||
| 229 | + // Initial request. Get filename from request parameters and return form. | ||
| 230 | + auto params = req->getParams(); | ||
| 231 | + std::string filename; | ||
| 232 | + bool hasFilename = params->getQueryParameter("filename", filename); | ||
| 233 | + std::string pathname = std::string("/public/") + filename; | ||
| 234 | + res->println("<html><head><title>Edit File</title><head><body>"); | ||
| 235 | + File file = SPIFFS.open(pathname.c_str()); | ||
| 236 | + if (!hasFilename) { | ||
| 237 | + res->println("<p>No filename specified.</p>"); | ||
| 238 | + } else if (!file.available()) { | ||
| 239 | + res->printf("<p>File not found: %s</p>\n", pathname.c_str()); | ||
| 240 | + } else { | ||
| 241 | + res->printf("<h2>Edit content of %s</h2>\n", pathname.c_str()); | ||
| 242 | + res->println("<form method=\"POST\" enctype=\"application/x-www-form-urlencoded\">"); | ||
| 243 | + res->printf("<input name=\"filename\" type=\"hidden\" value=\"%s\">", filename.c_str()); | ||
| 244 | + res->print("<textarea name=\"content\" rows=\"24\" cols=\"80\">"); | ||
| 245 | + // Read the file and write it to the response | ||
| 246 | + size_t length = 0; | ||
| 247 | + do { | ||
| 248 | + char buffer[256]; | ||
| 249 | + length = file.read((uint8_t *)buffer, 256); | ||
| 250 | + std::string bufferString(buffer, length); | ||
| 251 | + bufferString = htmlEncode(bufferString); | ||
| 252 | + res->write((uint8_t *)bufferString.c_str(), bufferString.size()); | ||
| 253 | + } while (length > 0); | ||
| 254 | + res->println("</textarea><br>"); | ||
| 255 | + res->println("<input type=\"submit\" value=\"Save\">"); | ||
| 256 | + res->println("</form>"); | ||
| 257 | + } | ||
| 258 | + res->println("</body></html>"); | ||
| 259 | + } else { // method != GET | ||
| 260 | + // Assume POST request. Contains submitted data. | ||
| 261 | + res->println("<html><head><title>File Edited</title><head><body><h1>File Edited</h1>"); | ||
| 262 | + HTTPURLEncodedBodyParser parser(req); | ||
| 263 | + std::string filename; | ||
| 264 | + bool savedFile = false; | ||
| 265 | + while(parser.nextField()) { | ||
| 266 | + std::string name = parser.getFieldName(); | ||
| 267 | + if (name == "filename") { | ||
| 268 | + char buf[512]; | ||
| 269 | + size_t readLength = parser.read((byte *)buf, 512); | ||
| 270 | + filename = std::string("/public/") + std::string(buf, readLength); | ||
| 271 | + } else if (name == "content") { | ||
| 272 | + if (filename == "") { | ||
| 273 | + res->println("<p>Error: form contained content before filename.</p>"); | ||
| 274 | + break; | ||
| 275 | + } | ||
| 276 | + size_t fieldLength = 0; | ||
| 277 | + File file = SPIFFS.open(filename.c_str(), "w"); | ||
| 278 | + savedFile = true; | ||
| 279 | + while (!parser.endOfField()) { | ||
| 280 | + byte buf[512]; | ||
| 281 | + size_t readLength = parser.read(buf, 512); | ||
| 282 | + file.write(buf, readLength); | ||
| 283 | + fieldLength += readLength; | ||
| 284 | + } | ||
| 285 | + file.close(); | ||
| 286 | + res->printf("<p>Saved %d bytes to %s</p>", int(fieldLength), filename.c_str()); | ||
| 287 | + } else { | ||
| 288 | + res->printf("<p>Unexpected field %s</p>", name.c_str()); | ||
| 289 | + } | ||
| 290 | + } | ||
| 291 | + if (!savedFile) { | ||
| 292 | + res->println("<p>No file to save...</p>"); | ||
| 293 | + } | ||
| 294 | + res->println("</body></html>"); | ||
| 295 | + } | ||
| 296 | + } | ||
| 297 | + | ||
| 298 | + void handleDirectory(HTTPRequest * req, HTTPResponse * res) { | ||
| 299 | + res->println("<html><head><title>File Listing</title><head><body>"); | ||
| 300 | + File d = SPIFFS.open("/public"); | ||
| 301 | + if (!d.isDirectory()) { | ||
| 302 | + res->println("<p>No files found.</p>"); | ||
| 303 | + } else { | ||
| 304 | + res->println("<h1>File Listing</h1>"); | ||
| 305 | + res->println("<ul>"); | ||
| 306 | + File f = d.openNextFile(); | ||
| 307 | + while (f) { | ||
| 308 | + std::string pathname(f.name()); | ||
| 309 | + res->printf("<li><a href=\"%s\">%s</a>", pathname.c_str(), pathname.c_str()); | ||
| 310 | + if (pathname.rfind(".txt") != std::string::npos) { | ||
| 311 | + std::string filename = pathname.substr(8); // Remove /public/ | ||
| 312 | + res->printf(" <a href=\"/edit?filename=%s\">[edit]</a>", filename.c_str()); | ||
| 313 | + } | ||
| 314 | + res->println("</li>"); | ||
| 315 | + f = d.openNextFile(); | ||
| 316 | + } | ||
| 317 | + res->println("</ul>"); | ||
| 318 | + } | ||
| 319 | + res->println("</body></html>"); | ||
| 320 | + } | ||
| 321 | + | ||
| 322 | + void handleFile(HTTPRequest * req, HTTPResponse * res) { | ||
| 323 | + std::string filename = req->getRequestString(); | ||
| 324 | + // Check if the file exists | ||
| 325 | + if (!SPIFFS.exists(filename.c_str())) { | ||
| 326 | + // Send "404 Not Found" as response, as the file doesn't seem to exist | ||
| 327 | + res->setStatusCode(404); | ||
| 328 | + res->setStatusText("Not found"); | ||
| 329 | + res->println("404 Not Found"); | ||
| 330 | + return; | ||
| 331 | + } | ||
| 332 | + | ||
| 333 | + File file = SPIFFS.open(filename.c_str()); | ||
| 334 | + | ||
| 335 | + // Set length | ||
| 336 | + res->setHeader("Content-Length", httpsserver::intToString(file.size())); | ||
| 337 | + | ||
| 338 | + // Content-Type is guessed using the definition of the contentTypes-table defined above | ||
| 339 | + int cTypeIdx = 0; | ||
| 340 | + do { | ||
| 341 | + if(filename.rfind(contentTypes[cTypeIdx][0])!=std::string::npos) { | ||
| 342 | + res->setHeader("Content-Type", contentTypes[cTypeIdx][1]); | ||
| 343 | + break; | ||
| 344 | + } | ||
| 345 | + cTypeIdx+=1; | ||
| 346 | + } while(strlen(contentTypes[cTypeIdx][0])>0); | ||
| 347 | + | ||
| 348 | + // Read the file and write it to the response | ||
| 349 | + uint8_t buffer[256]; | ||
| 350 | + size_t length = 0; | ||
| 351 | + do { | ||
| 352 | + length = file.read(buffer, 256); | ||
| 353 | + res->write(buffer, length); | ||
| 354 | + } while (length > 0); | ||
| 355 | + | ||
| 356 | + file.close(); | ||
| 357 | + } | ||
| 358 | + | ||
| 359 | + void handle404(HTTPRequest * req, HTTPResponse * res) { | ||
| 360 | + // Discard request body, if we received any | ||
| 361 | + // We do this, as this is the default node and may also server POST/PUT requests | ||
| 362 | + req->discardRequestBody(); | ||
| 363 | + | ||
| 364 | + // Set the response status | ||
| 365 | + res->setStatusCode(404); | ||
| 366 | + res->setStatusText("Not Found"); | ||
| 367 | + | ||
| 368 | + // Set content type of the response | ||
| 369 | + res->setHeader("Content-Type", "text/html"); | ||
| 370 | + | ||
| 371 | + // Write a tiny HTML page | ||
| 372 | + res->println("<!DOCTYPE html>"); | ||
| 373 | + res->println("<html>"); | ||
| 374 | + res->println("<head><title>Not Found</title></head>"); | ||
| 375 | + res->println("<body><h1>404 Not Found</h1><p>The requested resource was not found on this server.</p></body>"); | ||
| 376 | + res->println("</html>"); | ||
| 377 | + } | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments