|
| 1 | +# Arduino-ESP32 WebServer Example for WebServer Library |
| 2 | + |
| 3 | +This example shows different techniques on how to use and extend the WebServer for specific purposes |
| 4 | + |
| 5 | +It is a small project in it's own and has some files to use on the web server to show how to use simple REST based services. |
| 6 | + |
| 7 | +This example requires some space for a filesystem and runs fine boards with 4 MByte flash using the following options: |
| 8 | + |
| 9 | +* Board: ESP32 Dev Module |
| 10 | +* Partition Scheme: Default 4MB with spiffs (1.2MB APP/1.5MB SPIFFS) |
| 11 | + but LittleFS will be used in the partition (not SPIFFS) |
| 12 | + |
| 13 | +It features |
| 14 | + |
| 15 | +* Setup a web server |
| 16 | +* redirect when accessing the url with servername only |
| 17 | +* get real time by using builtin NTP functionality |
| 18 | +* send HTML responses from Sketch (see builtinfiles.h) |
| 19 | +* use a LittleFS file system on the data partition for static files |
| 20 | +* use http ETag Header for client side caching of static files |
| 21 | +* use custom ETag calculation for static files |
| 22 | +* extended FileServerHandler for uploading and deleting static files |
| 23 | +* uploading files using drag & drop |
| 24 | +* serve APIs using REST services (/api/list, /api/sysinfo) |
| 25 | +* define HTML response when no file/api/handler was found |
| 26 | + |
| 27 | +## Supported Targets |
| 28 | + |
| 29 | +Currently, this example supports the following targets. |
| 30 | + |
| 31 | +| Supported Targets | ESP32 | ESP32-S2 | ESP32-C3 | |
| 32 | +| ----------------- | ----- | -------- | -------- | |
| 33 | +| | yes | yes | yes | |
| 34 | + |
| 35 | +## Use the Example |
| 36 | + |
| 37 | +How to install the Arduino IDE: [Install Arduino IDE](https://github.com/espressif/arduino-esp32/tree/master/docs/arduino-ide). |
| 38 | + |
| 39 | +* In the file `secrets.h` you can add the home WiFi network name ans password. |
| 40 | +* Compile and upload to the device. |
| 41 | +* Have a look into the monitoring output. |
| 42 | +* Open <http://webserver> or <http://(ip-address)> using a browser. |
| 43 | +* You will be redirected to <http://webserver/$upload.htm> as there are no files yet in the file system. |
| 44 | +* Drag the files from the data folder onto the drop area shown in the browser. |
| 45 | +* See below for more details |
| 46 | + |
| 47 | +## Implementing a web server |
| 48 | + |
| 49 | +The WebServer library offers a simple path to implement a web server on a ESP32 based board. |
| 50 | + |
| 51 | +The advantage on using the WebServer instead of the plain simple WiFiServer is that the WebServer |
| 52 | +takes much care about the http protocol conventions and features and allows easily access to parameters. |
| 53 | +It offers plug-in capabilities by registering specific functionalities that will be outlined below. |
| 54 | + |
| 55 | +### Initialization |
| 56 | + |
| 57 | +In the setup() function in the webserver.ino sketch file the following steps are implemented to make the webserver available on the local network. |
| 58 | + |
| 59 | +* Create a webserver listening to port 80 for http requests. |
| 60 | +* Initialize the access to the filesystem in the free flash memory. |
| 61 | +* Connect to the local WiFi network. Here is only a straight-forward implementation hard-coding network name and passphrase. You may consider to use something like the WiFiManager library in real applications. |
| 62 | +* Register the device in DNS using a known hostname. |
| 63 | +* Registering several plug-ins (see below). |
| 64 | +* Starting the web server. |
| 65 | + |
| 66 | +### Running |
| 67 | + |
| 68 | +In the loop() function the web server will be given time to receive and send network packages by calling |
| 69 | +`server.handleClient();`. |
| 70 | + |
| 71 | +## Registering simple functions to implement RESTful services |
| 72 | + |
| 73 | +Registering function is the simplest integration mechanism available to add functionality. The server offers the `on(path, function)` methods that take the URL and the function as parameters. |
| 74 | + |
| 75 | +There are 2 functions implemented that get registered to handle incoming GET requests for given URLs. |
| 76 | + |
| 77 | +The JSON data format is used often for such services as it is the "natural" data format of the browser using javascript. |
| 78 | + |
| 79 | +When the **handleSysInfo()** function is registered and a browser requests for <http://webserver/api/sysinfo> the function will be called and can collect the requested information. |
| 80 | + |
| 81 | +> ```CPP |
| 82 | +> server.on("/api/sysinfo", handleSysInfo); |
| 83 | +> ``` |
| 84 | +
|
| 85 | +The result in this case is a JSON object that is assembled in the result String variable and the returned as a response to the client also giving the information about the data format. |
| 86 | +
|
| 87 | +You can try this request in a browser by opening <http://webserver/api/sysinfo> in the address bar. |
| 88 | +
|
| 89 | +> ```CPP |
| 90 | +> server.on("/api/sysinfo", handleList); |
| 91 | +> ``` |
| 92 | +
|
| 93 | +The function **handleList()** is registered the same way to return the list of files in the file system also returning a JSON object including name, size and the last modification timestamp. |
| 94 | +
|
| 95 | +You can try this request in a browser by opening <http://webserver/api/list> in the address bar. |
| 96 | +
|
| 97 | +## Registering a function to send out some static content from a String |
| 98 | +
|
| 99 | +This is an example of registering a inline function in the web server. |
| 100 | +The 2. parameter of the on() method is a so called CPP lamda function (without a name) |
| 101 | +that actually has only one line of functionality by sending a string as result to the client. |
| 102 | +
|
| 103 | +> ``` cpp |
| 104 | +> server.on("/$upload.htm", []() { |
| 105 | +> server.send(200, "text/html", FPSTR(uploadContent)); |
| 106 | +> }); |
| 107 | +> ``` |
| 108 | +
|
| 109 | +Here the text from a static String with html code is returned instead of a file from the filesystem. |
| 110 | +The content of this string can be found in the file `builtinfiles.h`. It contains a small html+javascript implementation |
| 111 | +that allows uploading new files into the empty filesystem. |
| 112 | +
|
| 113 | +Just open <http://webserver/$upload.htm> and drag some files from the data folder on the drop area. |
| 114 | +
|
| 115 | +## Registering a function to handle requests to the server without a path |
| 116 | +
|
| 117 | +Often servers are addressed by using the base URL like <http://webserver/> where no further path details is given. |
| 118 | +Of course we like the user to be redirected to something usable. Therefore the `handleRoot()` function is registered: |
| 119 | +
|
| 120 | +> ``` cpp |
| 121 | +> server.on("/$upload.htm", handleRoot); |
| 122 | +> ``` |
| 123 | +
|
| 124 | +The `handleRoot()` function checks the filesystem for the file named **/index.htm** and creates a redirect to this file when the file exists. |
| 125 | +Otherwise the redirection goes to the built-in **/$upload.htm** web page. |
| 126 | +
|
| 127 | +## Using the serveStatic plug-in |
| 128 | +
|
| 129 | +The **serveStatic** plug in is part of the library and handles delivering files from the filesystem to the client. It can be customized in some ways. |
| 130 | +
|
| 131 | +> ``` cpp |
| 132 | +> server.enableCORS(true); |
| 133 | +> server.enableETag(true); |
| 134 | +> server.serveStatic("/", LittleFS, "/"); |
| 135 | +> ``` |
| 136 | +
|
| 137 | +### Cross-Origin Ressource Sharing (CORS) |
| 138 | +
|
| 139 | +The `enableCORS(true)` function adds a `Access-Control-Allow-Origin: *` http-header to all responses to the client |
| 140 | +to inform that it is allowed to call URLs and services on this server from other web sites. |
| 141 | +
|
| 142 | +The feature is disabled by default (in the current version) and when you like to disable this then you should call `enableCORS(false)` during setup. |
| 143 | +
|
| 144 | +* Web sites providing high sensitive information like online banking this is disabled most of the times. |
| 145 | +* Web sites providing advertising information or reusable scripts / images this is enabled. |
| 146 | +
|
| 147 | +### enabling ETag support |
| 148 | +
|
| 149 | +To enable this in the embedded web server the `enableETag()` can be used. |
| 150 | +(next to enableCORS) |
| 151 | +
|
| 152 | +In the simplest version just call `enableETag(true)` to enable the internal ETag generation that calcs the hint using a md5 checksum in base64 encoded form. This is an simple approach that adds some time for calculation on every request but avoids network traffic. |
| 153 | +
|
| 154 | +The headers will look like: |
| 155 | +
|
| 156 | +``` txt |
| 157 | +If-None-Match: "GhZka3HevoaEBbtQOgOqlA==" |
| 158 | +ETag: "GhZka3HevoaEBbtQOgOqlA==" |
| 159 | +``` |
| 160 | +
|
| 161 | +
|
| 162 | +### ETag support customization |
| 163 | +
|
| 164 | +The enableETag() function has an optional second optional parameter to provide a function for ETag calculation of files. |
| 165 | +
|
| 166 | +The function enables eTags for all files by using calculating a value from the last write timestamp: |
| 167 | +
|
| 168 | +``` cpp |
| 169 | +server.enableETag(true, [](FS &fs, const String &path) -> String { |
| 170 | + File f = fs.open(path, "r"); |
| 171 | + String eTag = String(f.getLastWrite(), 16); // use file modification timestamp to create ETag |
| 172 | + f.close(); |
| 173 | + return (eTag); |
| 174 | +}); |
| 175 | +``` |
| 176 | +
|
| 177 | +The headers will look like: |
| 178 | +
|
| 179 | +``` txt |
| 180 | +ETag: "63bbaeb5" |
| 181 | +If-None-Match: "63bbaeb5" |
| 182 | +``` |
| 183 | +
|
| 184 | +
|
| 185 | +## Registering a full-featured handler as plug-in |
| 186 | +
|
| 187 | +The example also implements the class `FileServerHandler` derived from the class `RequestHandler` to plug in functionality |
| 188 | +that can handle more complex requests without giving a fixed URL. |
| 189 | +It implements uploading and deleting files in the file system that is not implemented by the standard server.serveStatic functionality. |
| 190 | +
|
| 191 | +This class has to implements several functions and works in a more detailed way: |
| 192 | +
|
| 193 | +* The `canHandle()` method can inspect the given http method and url to decide weather the RequestFileHandler can handle the incoming request or not. |
| 194 | +
|
| 195 | + In this case the RequestFileHandler will return true when the request method is an POST for upload or a DELETE for deleting files. |
| 196 | +
|
| 197 | + The regular GET requests will be ignored and therefore handled by the also registered server.serveStatic handler. |
| 198 | +
|
| 199 | +* The function `handle()` then implements the real deletion of the file. |
| 200 | +
|
| 201 | +* The `canUpload()`and `upload()` methods work similar while the `upload()` method is called multiple times to create, append data and close the new file. |
| 202 | +
|
| 203 | +## File upload |
| 204 | +
|
| 205 | +By opening <http://webserver/$upload.htm> you can easily upload files by dragging them over the drop area. |
| 206 | +
|
| 207 | +Just take the files from the data folder to create some files that can explore the server functionality. |
| 208 | +
|
| 209 | +Files will be uploaded to the root folder of the file system. and you will see it next time using <http://webserver/files.htm>. |
| 210 | +
|
| 211 | +The filesize that is uploaded is not known when the upload mechanism in function |
| 212 | +FileServerHandler::upload gets started. |
| 213 | +
|
| 214 | +Uploading a file that fits into the available filesystem space |
| 215 | +can be found in the Serial output: |
| 216 | +
|
| 217 | +``` txt |
| 218 | +starting upload file /file.txt... |
| 219 | +finished. |
| 220 | +1652 bytes uploaded. |
| 221 | +``` |
| 222 | +
|
| 223 | +Uploading a file that doesn't fit can be detected while uploading when writing to the filesystem fails. |
| 224 | +However upload cannot be aborted by the current handler implementation. |
| 225 | +
|
| 226 | +The solution implemented here is to delete the partially uploaded file and wait for the upload ending. |
| 227 | +The following can be found in the Serial output: |
| 228 | +
|
| 229 | +``` txt |
| 230 | +starting upload file /huge.jpg... |
| 231 | +./components/esp_littlefs/src/littlefs/lfs.c:584:error: No more free space 531 |
| 232 | + write error! |
| 233 | +finished. |
| 234 | +``` |
| 235 | +
|
| 236 | +You can see on the Serial output that one filesystem write error is reported. |
| 237 | +
|
| 238 | +Please be patient and wait for the upload ending even when writing to the filesystem is disabled |
| 239 | +it maybe take more than a minute. |
| 240 | +
|
| 241 | +## Registering a special handler for "file not found" |
| 242 | +
|
| 243 | +Any other incoming request that was not handled by the registered plug-ins above can be detected by registering |
| 244 | +
|
| 245 | +> ``` cpp |
| 246 | +> // handle cases when file is not found |
| 247 | +> server.onNotFound([]() { |
| 248 | +> // standard not found in browser. |
| 249 | +> server.send(404, "text/html", FPSTR(notFoundContent)); |
| 250 | +> }); |
| 251 | +> ``` |
| 252 | +
|
| 253 | +This allows sending back an "friendly" result for the browser. Here a simple html page is created from a static string. |
| 254 | +You can easily change the html code in the file `builtinfiles.h`. |
| 255 | +
|
| 256 | +## customizations |
| 257 | +
|
| 258 | +You may like to change the hostname and the timezone in the lines: |
| 259 | +
|
| 260 | +> ``` cpp |
| 261 | +> #define HOSTNAME "webserver" |
| 262 | +> #define TIMEZONE "CET-1CEST,M3.5.0,M10.5.0/3" |
| 263 | +> ``` |
| 264 | +
|
| 265 | +## Troubleshooting |
| 266 | +
|
| 267 | +Have a look in the Serial output for some additional runtime information. |
| 268 | +
|
| 269 | +## Contribute |
| 270 | +
|
| 271 | +To know how to contribute to this project, see [How to contribute.](https://github.com/espressif/arduino-esp32/blob/master/CONTRIBUTING.rst) |
| 272 | +
|
| 273 | +If you have any **feedback** or **issue** to report on this example/library, please open an issue or fix it by creating a new PR. Contributions are more than welcome! |
| 274 | +
|
| 275 | +Before creating a new issue, be sure to try Troubleshooting and check if the same issue was already created by someone else. |
| 276 | +
|
| 277 | +## Resources |
| 278 | +
|
| 279 | +* Official ESP32 Forum: [Link](https://esp32.com) |
| 280 | +* Arduino-ESP32 Official Repository: [espressif/arduino-esp32](https://github.com/espressif/arduino-esp32) |
| 281 | +* ESP32 Datasheet: [Link to datasheet](https://www.espressif.com/sites/default/files/documentation/esp32_datasheet_en.pdf) |
| 282 | +* ESP32-S2 Datasheet: [Link to datasheet](https://www.espressif.com/sites/default/files/documentation/esp32-s2_datasheet_en.pdf) |
| 283 | +* ESP32-C3 Datasheet: [Link to datasheet](https://www.espressif.com/sites/default/files/documentation/esp32-c3_datasheet_en.pdf) |
| 284 | +* Official ESP-IDF documentation: [ESP-IDF](https://idf.espressif.com) |
0 commit comments