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+ }
0 commit comments