| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,297 @@ | |||
| 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 a chat interface on the root node / | ||
| 14 | + * - Use a websocket to allow multiple clients to pass messages to each other | ||
| 15 | + */ | ||
| 16 | + | ||
| 17 | + #include <sstream> | ||
| 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 | + // Max clients to be connected to the chat | ||
| 24 | + #define MAX_CLIENTS 4 | ||
| 25 | + | ||
| 26 | + // Include certificate data (see note above) | ||
| 27 | + #include "cert.h" | ||
| 28 | + #include "private_key.h" | ||
| 29 | + | ||
| 30 | + // We will use wifi | ||
| 31 | + #include <WiFi.h> | ||
| 32 | + | ||
| 33 | + // Includes for the server | ||
| 34 | + #include <HTTPSServer.hpp> | ||
| 35 | + #include <SSLCert.hpp> | ||
| 36 | + #include <HTTPRequest.hpp> | ||
| 37 | + #include <HTTPResponse.hpp> | ||
| 38 | + #include <WebsocketHandler.hpp> | ||
| 39 | + | ||
| 40 | + // The HTTPS Server comes in a separate namespace. For easier use, include it here. | ||
| 41 | + using namespace httpsserver; | ||
| 42 | + | ||
| 43 | + // Create an SSL certificate object from the files included above ... | ||
| 44 | + SSLCert cert = SSLCert( | ||
| 45 | + example_crt_DER, example_crt_DER_len, | ||
| 46 | + example_key_DER, example_key_DER_len | ||
| 47 | + ); | ||
| 48 | + // ... and create a server based on this certificate. | ||
| 49 | + // The constructor has some optional parameters like the TCP port that should be used | ||
| 50 | + // and the max client count. For simplicity, we use a fixed amount of clients that is bound | ||
| 51 | + // to the max client count. | ||
| 52 | + HTTPSServer secureServer = HTTPSServer(&cert, 443, MAX_CLIENTS); | ||
| 53 | + | ||
| 54 | + // Declare some handler functions for the various URLs on the server | ||
| 55 | + // The signature is always the same for those functions. They get two parameters, | ||
| 56 | + // which are pointers to the request data (read request body, headers, ...) and | ||
| 57 | + // to the response data (write response, set status code, ...) | ||
| 58 | + void handleRoot(HTTPRequest * req, HTTPResponse * res); | ||
| 59 | + void handle404(HTTPRequest * req, HTTPResponse * res); | ||
| 60 | + | ||
| 61 | + // As websockets are more complex, they need a custom class that is derived from WebsocketHandler | ||
| 62 | + class ChatHandler : public WebsocketHandler { | ||
| 63 | + public: | ||
| 64 | + // This method is called by the webserver to instantiate a new handler for each | ||
| 65 | + // client that connects to the websocket endpoint | ||
| 66 | + static WebsocketHandler* create(); | ||
| 67 | + | ||
| 68 | + // This method is called when a message arrives | ||
| 69 | + void onMessage(WebsocketInputStreambuf * input); | ||
| 70 | + | ||
| 71 | + // Handler function on connection close | ||
| 72 | + void onClose(); | ||
| 73 | + }; | ||
| 74 | + | ||
| 75 | + // Simple array to store the active clients: | ||
| 76 | + ChatHandler* activeClients[MAX_CLIENTS]; | ||
| 77 | + | ||
| 78 | + void setup() { | ||
| 79 | + // Initialize the slots | ||
| 80 | + for(int i = 0; i < MAX_CLIENTS; i++) activeClients[i] = nullptr; | ||
| 81 | + | ||
| 82 | + // For logging | ||
| 83 | + Serial.begin(115200); | ||
| 84 | + | ||
| 85 | + // Connect to WiFi | ||
| 86 | + Serial.println("Setting up WiFi"); | ||
| 87 | + WiFi.begin(WIFI_SSID, WIFI_PSK); | ||
| 88 | + while (WiFi.status() != WL_CONNECTED) { | ||
| 89 | + Serial.print("."); | ||
| 90 | + delay(500); | ||
| 91 | + } | ||
| 92 | + Serial.print("Connected. IP="); | ||
| 93 | + Serial.println(WiFi.localIP()); | ||
| 94 | + | ||
| 95 | + // For every resource available on the server, we need to create a ResourceNode | ||
| 96 | + // The ResourceNode links URL and HTTP method to a handler function | ||
| 97 | + ResourceNode * nodeRoot = new ResourceNode("/", "GET", &handleRoot); | ||
| 98 | + ResourceNode * node404 = new ResourceNode("", "GET", &handle404); | ||
| 99 | + | ||
| 100 | + // Add the root node to the server | ||
| 101 | + secureServer.registerNode(nodeRoot); | ||
| 102 | + | ||
| 103 | + // The websocket handler can be linked to the server by using a WebsocketNode: | ||
| 104 | + // (Note that the standard defines GET as the only allowed method here, | ||
| 105 | + // so you do not need to pass it explicitly) | ||
| 106 | + WebsocketNode * chatNode = new WebsocketNode("/chat", &ChatHandler::create); | ||
| 107 | + | ||
| 108 | + // Adding the node to the server works in the same way as for all other nodes | ||
| 109 | + secureServer.registerNode(chatNode); | ||
| 110 | + | ||
| 111 | + // Finally, add the 404 not found node to the server. | ||
| 112 | + // The path is ignored for the default node. | ||
| 113 | + secureServer.setDefaultNode(node404); | ||
| 114 | + | ||
| 115 | + Serial.println("Starting server..."); | ||
| 116 | + secureServer.start(); | ||
| 117 | + if (secureServer.isRunning()) { | ||
| 118 | + Serial.print("Server ready. Open the following URL in multiple browser windows to start chatting: https://"); | ||
| 119 | + Serial.println(WiFi.localIP()); | ||
| 120 | + } | ||
| 121 | + } | ||
| 122 | + | ||
| 123 | + void loop() { | ||
| 124 | + // This call will let the server do its work | ||
| 125 | + secureServer.loop(); | ||
| 126 | + | ||
| 127 | + // Other code would go here... | ||
| 128 | + delay(1); | ||
| 129 | + } | ||
| 130 | + | ||
| 131 | + void handle404(HTTPRequest * req, HTTPResponse * res) { | ||
| 132 | + // Discard request body, if we received any | ||
| 133 | + // We do this, as this is the default node and may also server POST/PUT requests | ||
| 134 | + req->discardRequestBody(); | ||
| 135 | + | ||
| 136 | + // Set the response status | ||
| 137 | + res->setStatusCode(404); | ||
| 138 | + res->setStatusText("Not Found"); | ||
| 139 | + | ||
| 140 | + // Set content type of the response | ||
| 141 | + res->setHeader("Content-Type", "text/html"); | ||
| 142 | + | ||
| 143 | + // Write a tiny HTTP page | ||
| 144 | + res->println("<!DOCTYPE html>"); | ||
| 145 | + res->println("<html>"); | ||
| 146 | + res->println("<head><title>Not Found</title></head>"); | ||
| 147 | + res->println("<body><h1>404 Not Found</h1><p>The requested resource was not found on this server.</p></body>"); | ||
| 148 | + res->println("</html>"); | ||
| 149 | + } | ||
| 150 | + | ||
| 151 | + // In the create function of the handler, we create a new Handler and keep track | ||
| 152 | + // of it using the activeClients array | ||
| 153 | + WebsocketHandler * ChatHandler::create() { | ||
| 154 | + Serial.println("Creating new chat client!"); | ||
| 155 | + ChatHandler * handler = new ChatHandler(); | ||
| 156 | + for(int i = 0; i < MAX_CLIENTS; i++) { | ||
| 157 | + if (activeClients[i] == nullptr) { | ||
| 158 | + activeClients[i] = handler; | ||
| 159 | + break; | ||
| 160 | + } | ||
| 161 | + } | ||
| 162 | + return handler; | ||
| 163 | + } | ||
| 164 | + | ||
| 165 | + // When the websocket is closing, we remove the client from the array | ||
| 166 | + void ChatHandler::onClose() { | ||
| 167 | + for(int i = 0; i < MAX_CLIENTS; i++) { | ||
| 168 | + if (activeClients[i] == this) { | ||
| 169 | + activeClients[i] = nullptr; | ||
| 170 | + } | ||
| 171 | + } | ||
| 172 | + } | ||
| 173 | + | ||
| 174 | + // Finally, passing messages around. If we receive something, we send it to all | ||
| 175 | + // other clients | ||
| 176 | + void ChatHandler::onMessage(WebsocketInputStreambuf * inbuf) { | ||
| 177 | + // Get the input message | ||
| 178 | + std::ostringstream ss; | ||
| 179 | + std::string msg; | ||
| 180 | + ss << inbuf; | ||
| 181 | + msg = ss.str(); | ||
| 182 | + | ||
| 183 | + // Send it back to every client | ||
| 184 | + for(int i = 0; i < MAX_CLIENTS; i++) { | ||
| 185 | + if (activeClients[i] != nullptr) { | ||
| 186 | + activeClients[i]->send(msg, SEND_TYPE_TEXT); | ||
| 187 | + } | ||
| 188 | + } | ||
| 189 | + } | ||
| 190 | + | ||
| 191 | + | ||
| 192 | + | ||
| 193 | + // The following HTML code will present the chat interface. | ||
| 194 | + void handleRoot(HTTPRequest * req, HTTPResponse * res) { | ||
| 195 | + res->setHeader("Content-Type", "text/html"); | ||
| 196 | + | ||
| 197 | + res->print( | ||
| 198 | + "<!DOCTYPE HTML>\n" | ||
| 199 | + "<html>\n" | ||
| 200 | + " <head>\n" | ||
| 201 | + " <title>ESP32 Chat</title>\n" | ||
| 202 | + "</head>\n" | ||
| 203 | + "<body>\n" | ||
| 204 | + " <div style=\"width:500px;border:1px solid black;margin:20px auto;display:block\">\n" | ||
| 205 | + " <form onsubmit=\"return false\">\n" | ||
| 206 | + " Your Name: <input type=\"text\" id=\"txtName\" value=\"ESP32 user\">\n" | ||
| 207 | + " <button type=\"submit\" id=\"btnConnect\">Connect</button>\n" | ||
| 208 | + " </form>\n" | ||
| 209 | + " <form onsubmit=\"return false\">\n" | ||
| 210 | + " <div style=\"overflow:scroll;height:400px\" id=\"divOut\">Not connected...</div>\n" | ||
| 211 | + " Your Message: <input type=\"text\" id=\"txtChat\" disabled>\n" | ||
| 212 | + " <button type=\"submit\" id=\"btnSend\" disabled>Send</button>\n" | ||
| 213 | + " </form>\n" | ||
| 214 | + " </div>\n" | ||
| 215 | + " <script type=\"text/javascript\">\n" | ||
| 216 | + " const elem = id => document.getElementById(id);\n" | ||
| 217 | + " const txtName = elem(\"txtName\");\n" | ||
| 218 | + " const txtChat = elem(\"txtChat\");\n" | ||
| 219 | + " const btnConnect = elem(\"btnConnect\");\n" | ||
| 220 | + " const btnSend = elem(\"btnSend\");\n" | ||
| 221 | + " const divOut = elem(\"divOut\");\n" | ||
| 222 | + "\n" | ||
| 223 | + " class Chat {\n" | ||
| 224 | + " constructor() {\n" | ||
| 225 | + " this.connecting = false;\n" | ||
| 226 | + " this.connected = false;\n" | ||
| 227 | + " this.name = \"\";\n" | ||
| 228 | + " this.ws = null;\n" | ||
| 229 | + " }\n" | ||
| 230 | + " connect() {\n" | ||
| 231 | + " if (this.ws === null) {\n" | ||
| 232 | + " this.connecting = true;\n" | ||
| 233 | + " txtName.disabled = true;\n" | ||
| 234 | + " this.name = txtName.value;\n" | ||
| 235 | + " btnConnect.innerHTML = \"Connecting...\";\n" | ||
| 236 | + " this.ws = new WebSocket(\"wss://" | ||
| 237 | + ); | ||
| 238 | + res->print(WiFi.localIP()); | ||
| 239 | + res->print( | ||
| 240 | + "/chat\");\n" | ||
| 241 | + " this.ws.onopen = e => {\n" | ||
| 242 | + " this.connecting = false;\n" | ||
| 243 | + " this.connected = true;\n" | ||
| 244 | + " divOut.innerHTML = \"<p>Connected.</p>\";\n" | ||
| 245 | + " btnConnect.innerHTML = \"Disconnect\";\n" | ||
| 246 | + " txtChat.disabled=false;\n" | ||
| 247 | + " btnSend.disabled=false;\n" | ||
| 248 | + " this.ws.send(this.name + \" joined!\");\n" | ||
| 249 | + " };\n" | ||
| 250 | + " this.ws.onmessage = e => {\n" | ||
| 251 | + " divOut.innerHTML+=\"<p>\"+e.data+\"</p>\";\n" | ||
| 252 | + " divOut.scrollTo(0,divOut.scrollHeight);\n" | ||
| 253 | + " }\n" | ||
| 254 | + " this.ws.onclose = e => {\n" | ||
| 255 | + " this.disconnect();\n" | ||
| 256 | + " }\n" | ||
| 257 | + " }\n" | ||
| 258 | + " }\n" | ||
| 259 | + " disconnect() {\n" | ||
| 260 | + " if (this.ws !== null) {\n" | ||
| 261 | + " this.ws.send(this.name + \" left!\");\n" | ||
| 262 | + " this.ws.close();\n" | ||
| 263 | + " this.ws = null;\n" | ||
| 264 | + " }\n" | ||
| 265 | + " if (this.connected) {\n" | ||
| 266 | + " this.connected = false;\n" | ||
| 267 | + " txtChat.disabled=true;\n" | ||
| 268 | + " btnSend.disabled=true;\n" | ||
| 269 | + " txtName.disabled = false;\n" | ||
| 270 | + " divOut.innerHTML+=\"<p>Disconnected.</p>\";\n" | ||
| 271 | + " btnConnect.innerHTML = \"Connect\";\n" | ||
| 272 | + " }\n" | ||
| 273 | + " }\n" | ||
| 274 | + " sendMessage(msg) {\n" | ||
| 275 | + " if (this.ws !== null) {\n" | ||
| 276 | + " this.ws.send(this.name + \": \" + msg);\n" | ||
| 277 | + " }\n" | ||
| 278 | + " }\n" | ||
| 279 | + " };\n" | ||
| 280 | + " let chat = new Chat();\n" | ||
| 281 | + " btnConnect.onclick = () => {\n" | ||
| 282 | + " if (chat.connected) {\n" | ||
| 283 | + " chat.disconnect();\n" | ||
| 284 | + " } else if (!chat.connected && !chat.connecting) {\n" | ||
| 285 | + " chat.connect();\n" | ||
| 286 | + " }\n" | ||
| 287 | + " }\n" | ||
| 288 | + " btnSend.onclick = () => {\n" | ||
| 289 | + " chat.sendMessage(txtChat.value);\n" | ||
| 290 | + " txtChat.value=\"\";\n" | ||
| 291 | + " txtChat.focus();\n" | ||
| 292 | + " }\n" | ||
| 293 | + " </script>\n" | ||
| 294 | + "</body>\n" | ||
| 295 | + "</html>\n" | ||
| 296 | + ); | ||
| 297 | + } | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments