|
| 1 | +# WebServer example documentation and hints |
| 2 | + |
| 3 | +This example shows different techniques on how to use and extend the ESP8266WebServer 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 | +It requires some space for a filesystem and runs fine on ESP8266 NodeMCU board with 4 MByte flash using the following options: |
| 8 | +* Flash Size Option 4MB (FS:2MB) |
| 9 | +* Debug Port Serial |
| 10 | +* MMU 32+32 balanced |
| 11 | + |
| 12 | +It features |
| 13 | + |
| 14 | +* http access to the web server |
| 15 | +* deliver all files from the file system |
| 16 | +* deliver special built-in files |
| 17 | +* implement services (list files, sysinfo) |
| 18 | +* uploading files using drag & drop |
| 19 | +* listing and deleting files using a SPA application |
| 20 | +* Example of SPA and Web Service application |
| 21 | +* Only files in the root folder are supported for simplicity - no directories. |
| 22 | + |
| 23 | + |
| 24 | + |
| 25 | + |
| 26 | +## Implementing a web server |
| 27 | + |
| 28 | +The ESP8266WebServer library offers a simple path to implement a web server on a ESP8266 board. |
| 29 | + |
| 30 | +The advantage on using the ESP8266WebServer instead of the plain simple WiFiServer is that the ESP8266WebServer |
| 31 | +takes much care about the http protocol conventions and features and allows easily access to parameters. |
| 32 | +It offers plug-in capabilities by registering specific functionalities that will be outlined below. |
| 33 | + |
| 34 | + |
| 35 | +### Initialization |
| 36 | + |
| 37 | +In the setup() function in the webserver.ino sketch file the following steps are implemented to make the webserver available on the local network. |
| 38 | + |
| 39 | +* Create a webserver listening to port 80 for http requests. |
| 40 | +* Initialize the access to the filesystem in the free flash memory (typically 2MByte). |
| 41 | +* 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. |
| 42 | +* Register the device in DNS using a known hostname. |
| 43 | +* Registering several plug-ins (see below). |
| 44 | +* Starting the web server. |
| 45 | + |
| 46 | + |
| 47 | +### Running |
| 48 | + |
| 49 | +In the loop() function the web server will be given time to receive and send network packages by calling |
| 50 | +`server.handleClient();`. |
| 51 | + |
| 52 | + |
| 53 | + |
| 54 | +## Registering simple functions to implement RESTful services |
| 55 | + |
| 56 | +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. |
| 57 | + |
| 58 | +There are 2 functions implemented that get registered to handle incoming GET requests for given URLs. |
| 59 | + |
| 60 | +The JSON data format is used often for such services as it is the "natural" data format of the browser using javascript. |
| 61 | + |
| 62 | +When the **handleSysInfo()** function is registered and a browser requests for <http://webserver/$sysinfo> the function will be called and can collect the requested information. |
| 63 | + |
| 64 | +> ```CPP |
| 65 | +> server.on("/$sysinfo", handleSysInfo); |
| 66 | +> ``` |
| 67 | +
|
| 68 | +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. |
| 69 | +
|
| 70 | +You can try this request in a browser by opening <http://webserver/$sysinfo> in the address bar. |
| 71 | +
|
| 72 | +> ```CPP |
| 73 | +> server.on("/$sysinfo", handleList); |
| 74 | +> ``` |
| 75 | +
|
| 76 | +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. |
| 77 | +
|
| 78 | +You can try this request in a browser by opening <http://webserver/$list> in the address bar. |
| 79 | +
|
| 80 | +
|
| 81 | +## Registering a function to send out some static content from a String |
| 82 | +
|
| 83 | +This is an example of registering a inline function in the web server. |
| 84 | +The 2. parameter of the on() method is a so called CPP lamda function (without a name) |
| 85 | +that actually has only one line of functionality by sending a string as result to the client. |
| 86 | +
|
| 87 | +> ```CPP |
| 88 | +> server.on("/$upload.htm", []() { |
| 89 | +> server.send(200, "text/html", FPSTR(uploadContent)); |
| 90 | +> }); |
| 91 | +> ``` |
| 92 | +
|
| 93 | +Here the text from a static String with html code is returned instead of a file from the filesystem. |
| 94 | +The content of this string can be found in the file `builtinfiles.h`. It contains a small html+javascript implementation |
| 95 | +that allows uploading new files into the empty filesystem. |
| 96 | +
|
| 97 | +Just open <http://webserver/$upload.htm> and drag some files from the data folder on the drop area. |
| 98 | +
|
| 99 | +
|
| 100 | +## Registering a function to handle requests to the server without a path |
| 101 | +
|
| 102 | +Often servers are addressed by using the base URL like <http://webserver/> where no further path details is given. |
| 103 | +Of course we like the user to be redirected to something usable. Therefore the `handleRoot()` function is registered: |
| 104 | +
|
| 105 | +> ```CPP |
| 106 | +> server.on("/$upload.htm", handleRoot); |
| 107 | +> ``` |
| 108 | +
|
| 109 | +The `handleRoot()` function checks the filesystem for the file named **/index.htm** and creates a redirect to this file when the file exists. |
| 110 | +Otherwise the redirection goes to the built-in **/$upload.htm** web page. |
| 111 | +
|
| 112 | +
|
| 113 | +
|
| 114 | +## Using the serveStatic plug-in |
| 115 | +
|
| 116 | +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. |
| 117 | +
|
| 118 | +> ```CPP |
| 119 | +> server.enableCORS(true); |
| 120 | +> server.enableETag(true); |
| 121 | +> server.serveStatic("/", LittleFS, "/"); |
| 122 | +> ``` |
| 123 | +
|
| 124 | +
|
| 125 | +### Cross-Origin Ressource Sharing (CORS) |
| 126 | +
|
| 127 | +The `enableCORS(true)` function adds a `Access-Control-Allow-Origin: *` http-header to all responses to the client |
| 128 | +to inform that it is allowed to call URLs and services on this server from other web sites. |
| 129 | +
|
| 130 | +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. |
| 131 | +
|
| 132 | +* Web sites providing high sensitive information like online banking this is disabled most of the times. |
| 133 | +* Web sites providing advertising information or reusable scripts / images this is enabled. |
| 134 | +
|
| 135 | +
|
| 136 | +### ETag support |
| 137 | +
|
| 138 | +The `enableETag(true)` function adds a ETag http header to the responses to the client that come from files from the filesystem |
| 139 | +to enable better use of the cache in the browser. |
| 140 | +
|
| 141 | +When enabled by default the server reads the file content and creates a checksum using the md5 and base64 algorithm her called the ETag value |
| 142 | +that changes whenever the file contains something different. |
| 143 | +
|
| 144 | +Once a browser has got the content of a file from the server including the ETag information it will add that ETag value is added in the following requests for the same resource. |
| 145 | +Now the server can answer with a 'use the version from the cache' when the new calculated ETag value is equal to the ETag value in the request. |
| 146 | +
|
| 147 | +The calculation of the ETag value requires some time and processing but sending content is always slower. |
| 148 | +So when you have the situation that a browser will use a web server multiple times this mechanism saves network and computing and makes web pages more responsive. |
| 149 | +
|
| 150 | +In the source code you can find another version of an algorithm to calculate a ETag value that uses the date&time from the filesystem. |
| 151 | +This is a simpler and faster way but with a low risk of dismissing a file update as the timestamp is based on seconds and local time. |
| 152 | +This can be enabled on demand, see inline comments. |
| 153 | +
|
| 154 | +
|
| 155 | +## Registering a full-featured handler as plug-in |
| 156 | +
|
| 157 | +The example also implements the class `FileServerHandler` derived from the class `RequestHandler` to plug in functionality |
| 158 | +that can handle more complex requests without giving a fixed URL. |
| 159 | +It implements uploading and deleting files in the file system that is not implemented by the standard server.serveStatic functionality. |
| 160 | +
|
| 161 | +This class has to implements several functions and works in a more detailed way: |
| 162 | +
|
| 163 | +* The `canHandle()` method can inspect the given http method and url to decide weather the RequestFileHandler can handle the incoming request or not. |
| 164 | +
|
| 165 | + In this case the RequestFileHandler will return true when the request method is an POST for upload or a DELETE for deleting files. |
| 166 | +
|
| 167 | + The regular GET requests will be ignored and therefore handled by the also registered server.serveStatic handler. |
| 168 | +
|
| 169 | +* The function `handle()` then implements the real deletion of the file. |
| 170 | +
|
| 171 | +* The `canUpload()`and `upload()` methods work similar while the `upload()` method is called multiple times to create, append data and close the new file. |
| 172 | +
|
| 173 | +
|
| 174 | +## Registering a special handler for "file not found" |
| 175 | +
|
| 176 | +Any other incoming request that was not handled by the registered plug-ins above can be detected by registering |
| 177 | +
|
| 178 | +> ```CPP |
| 179 | +> // handle cases when file is not found |
| 180 | +> server.onNotFound([]() { |
| 181 | +> // standard not found in browser. |
| 182 | +> server.send(404, "text/html", FPSTR(notFoundContent)); |
| 183 | +> }); |
| 184 | +> ``` |
| 185 | +
|
| 186 | +This allows sending back an "friendly" result for the browser. Here a sim ple html page is created from a static string. |
| 187 | +You can easily change the html code in the file `builtinfiles.h`. |
| 188 | +
|
| 189 | +
|
| 190 | +## customizations |
| 191 | +
|
| 192 | +You may like to change the hostname and the timezone in the lines: |
| 193 | +
|
| 194 | +> ```CPP |
| 195 | +> #define HOSTNAME "webserver" |
| 196 | +> #define TIMEZONE "CET-1CEST,M3.5.0,M10.5.0/3" |
| 197 | +> ``` |
| 198 | +
|
| 199 | +
|
0 commit comments