esp32-warm-water
esp32 based project for the control of a heating element based on temperature
webserver.c
Go to the documentation of this file.
1 /*
2  * MIT License
3  *
4  * Copyright (c) 2021 wolffshots
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to deal
8  * in the Software without restriction, including without limitation the rights
9  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10  * copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in all
14  * copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22  * SOFTWARE.
23  */
24 
30 // rest of the includes
31 #include "webserver.h"
32 #include <stdio.h>
33 #include <string.h>
34 #include <sys/param.h>
35 #include <sys/unistd.h>
36 #include <sys/stat.h>
37 #include <dirent.h>
38 
39 #include "esp_log.h"
40 
41 #include "esp_vfs.h"
42 #include "esp_spiffs.h"
43 #include "esp_http_server.h"
44 
45 // variables
46 static const char *TAG = CONFIG_WEBSERVER_LOG_TAG;
47 
48 /* Max length a file path can have on storage */
49 #define FILE_PATH_MAX (ESP_VFS_PATH_MAX + CONFIG_SPIFFS_OBJ_NAME_LEN)
50 
51 /* Max size of an individual file. Make sure this
52  * value is same as that set in upload_script.html */
53 #define MAX_FILE_SIZE (200 * 1024) // 200 KB
54 #define MAX_FILE_SIZE_STR "200KB"
55 
56 /* Scratch buffer size */
57 #define SCRATCH_BUFSIZE 8192
58 
60 {
61  /* Base path of file storage */
62  char base_path[ESP_VFS_PATH_MAX + 1];
63 
64  /* Scratch buffer for temporary storage during file transfer */
66 };
67 
68 // function definitions
69 /* Copies the full path into destination buffer and returns
70  * pointer to path (skipping the preceding base path) */
71 static const char *get_path_from_uri(char *dest, const char *base_path, const char *uri, size_t destsize)
72 {
73  const size_t base_pathlen = strlen(base_path);
74  size_t pathlen = strlen(uri);
75 
76  const char *quest = strchr(uri, '?');
77  if (quest)
78  {
79  pathlen = MIN(pathlen, quest - uri);
80  }
81  const char *hash = strchr(uri, '#');
82  if (hash)
83  {
84  pathlen = MIN(pathlen, hash - uri);
85  }
86 
87  if (base_pathlen + pathlen + 1 > destsize)
88  {
89  /* Full path string won't fit into destination buffer */
90  return NULL;
91  }
92 
93  /* Construct full path (base + path) */
94  strcpy(dest, base_path);
95  strlcpy(dest + base_pathlen, uri, pathlen + 1);
96 
97  /* Return pointer to path, skipping the base */
98  return dest + base_pathlen;
99 }
100 /* Handler to redirect incoming GET request for /index.html to /
101  * This can be overridden by uploading file with same name */
102 static esp_err_t index_html_get_handler(httpd_req_t *req)
103 {
104  httpd_resp_set_status(req, "307 Temporary Redirect");
105  httpd_resp_set_hdr(req, "Location", "/");
106  httpd_resp_send(req, NULL, 0); // Response body can be empty
107  return ESP_OK;
108 }
109 
110 /* Handler to respond with an icon file embedded in flash.
111  * Browsers expect to GET website icon at URI /favicon.ico.
112  * This can be overridden by uploading file with same name */
113 // static esp_err_t favicon_get_handler(httpd_req_t *req)
114 // {
115 // extern const unsigned char favicon_ico_start[] asm("_binary_favicon_ico_start");
116 // extern const unsigned char favicon_ico_end[] asm("_binary_favicon_ico_end");
117 // const size_t favicon_ico_size = (favicon_ico_end - favicon_ico_start);
118 // httpd_resp_set_type(req, "image/x-icon");
119 // httpd_resp_send(req, (const char *)favicon_ico_start, favicon_ico_size);
120 // return ESP_OK;
121 // }
122 
123 /* font handler */
124 static esp_err_t font_regular_get_handler(httpd_req_t *req)
125 {
126  extern const unsigned char font_regular_start[] asm("_binary_overpass_regular_otf_start");
127  extern const unsigned char font_regular_end[] asm("_binary_overpass_regular_otf_end");
128  const size_t font_regular_size = (font_regular_end - font_regular_start);
129  httpd_resp_set_type(req, "font/otf");
130  httpd_resp_send(req, (const char *)font_regular_start, font_regular_size);
131  return ESP_OK;
132 }
133 static esp_err_t font_get_handler(httpd_req_t *req)
134 {
135  extern const unsigned char font_start[] asm("_binary_overpass_otf_start");
136  extern const unsigned char font_end[] asm("_binary_overpass_otf_end");
137  const size_t font_size = (font_end - font_start);
138  httpd_resp_set_type(req, "font/otf");
139  httpd_resp_send(req, (const char *)font_start, font_size);
140  return ESP_OK;
141 }
142 
143 /* Set HTTP response content type according to file extension */
144 static esp_err_t set_content_type_from_file(httpd_req_t *req, const char *filename)
145 {
146  if (IS_FILE_EXT(filename, ".pdf"))
147  {
148  return httpd_resp_set_type(req, "application/pdf");
149  }
150  else if (IS_FILE_EXT(filename, ".html"))
151  {
152  return httpd_resp_set_type(req, "text/html");
153  }
154  else if (IS_FILE_EXT(filename, ".jpeg"))
155  {
156  return httpd_resp_set_type(req, "image/jpeg");
157  }
158  else if (IS_FILE_EXT(filename, ".ico"))
159  {
160  return httpd_resp_set_type(req, "image/x-icon");
161  }
162  /* This is a limited set only */
163  /* For any other type always set as plain text */
164  return httpd_resp_set_type(req, "text/plain");
165 }
166 
167 off_t ws_get_file_size(const char *filename)
168 {
169  struct stat st;
170  if (stat(filename, &st) == 0)
171  {
172  ESP_LOGI(TAG, "file size: %ld", st.st_size);
173  return st.st_size;
174  }
175  ESP_LOGI(TAG, "Cannot determine size of %s\n",
176  filename);
177  return -1;
178 }
179 
180 static esp_err_t http_resp_index_html(httpd_req_t *req, const char *dirpath)
181 {
182  extern const unsigned char index_start[] asm("_binary_index_html_start");
183  extern const unsigned char index_end[] asm("_binary_index_html_end");
184  const size_t index_size = (index_end - index_start);
185  httpd_resp_send_chunk(req, (const char *)index_start, index_size);
186  httpd_resp_sendstr_chunk(req, NULL);
187  return ESP_OK;
188 }
189 static esp_err_t http_resp_robots_txt(httpd_req_t *req, const char *dirpath)
190 {
191  extern const unsigned char robots_start[] asm("_binary_robots_txt_start");
192  extern const unsigned char robots_end[] asm("_binary_robots_txt_end");
193  const size_t robots_size = (robots_end - robots_start);
194  httpd_resp_send_chunk(req, (const char *)robots_start, robots_size);
195  httpd_resp_sendstr_chunk(req, NULL);
196  return ESP_OK;
197 }
198 static esp_err_t http_resp_bundle_js(httpd_req_t *req, const char *dirpath)
199 {
200  extern const unsigned char bundle_start[] asm("_binary_bundle_js_start");
201  extern const unsigned char bundle_end[] asm("_binary_bundle_js_end");
202  const size_t bundle_size = (bundle_end - bundle_start);
203  httpd_resp_set_type(req, "text/javascript");
204  httpd_resp_send_chunk(req, (const char *)bundle_start, bundle_size);
205  httpd_resp_sendstr_chunk(req, NULL);
206  return ESP_OK;
207 }
208 static esp_err_t http_resp_bundle_js_map(httpd_req_t *req, const char *dirpath)
209 {
210  extern const unsigned char bundle_js_map_start[] asm("_binary_bundle_js_map_start");
211  extern const unsigned char bundle_js_map_end[] asm("_binary_bundle_js_map_end");
212  const size_t bundle_js_map_size = (bundle_js_map_end - bundle_js_map_start);
213  httpd_resp_set_type(req, "text/javascript");
214  httpd_resp_send_chunk(req, (const char *)bundle_js_map_start, bundle_js_map_size);
215  httpd_resp_sendstr_chunk(req, NULL);
216  return ESP_OK;
217 }
218 static esp_err_t http_resp_bundle_css(httpd_req_t *req, const char *dirpath)
219 {
220  extern const unsigned char bundle_css_start[] asm("_binary_bundle_css_start");
221  extern const unsigned char bundle_css_end[] asm("_binary_bundle_css_end");
222  const size_t bundle_css_size = (bundle_css_end - bundle_css_start);
223  httpd_resp_set_type(req, "text/css");
224  httpd_resp_send_chunk(req, (const char *)bundle_css_start, bundle_css_size);
225  httpd_resp_sendstr_chunk(req, NULL);
226  return ESP_OK;
227 }
228 static esp_err_t http_resp_global_css(httpd_req_t *req, const char *dirpath)
229 {
230  extern const unsigned char global_start[] asm("_binary_global_css_start");
231  extern const unsigned char global_end[] asm("_binary_global_css_end");
232  const size_t global_size = (global_end - global_start);
233  httpd_resp_set_type(req, "text/css");
234  httpd_resp_send_chunk(req, (const char *)global_start, global_size);
235  httpd_resp_sendstr_chunk(req, NULL);
236  return ESP_OK;
237 }
238 
239 static esp_err_t update_get_handler(httpd_req_t *req)
240 {
241  ESP_LOGD(TAG, "update received %s", req->uri);
242  httpd_resp_set_status(req, "200 OK");
243  httpd_resp_set_type(req, "text/html; charset=UTF-8");
244  char line[20];
245  sprintf(line, "%0.3f %0.1f %0.1f %0.1f", temp, goal, under, over);
246  httpd_resp_sendstr(req, line);
247  return ESP_OK;
248 }
249 
250 /* Handler to download a file kept on the server */
251 static esp_err_t download_get_handler(httpd_req_t *req)
252 {
253  ESP_LOGI(TAG, "download for %s", req->uri);
254  char filepath[FILE_PATH_MAX];
255  FILE *fd = NULL;
256  struct stat file_stat;
257 
258  const char *filename = get_path_from_uri(filepath, ((struct file_server_data *)req->user_ctx)->base_path,
259  req->uri, sizeof(filepath));
260  if (!filename)
261  {
262  ESP_LOGE(TAG, "Filename is too long");
263  /* Respond with 500 Internal Server Error */
264  httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Filename too long");
265  return ESP_FAIL;
266  }
267 
268  /* If name has trailing '/', respond with index.html */
269  if (filename[strlen(filename) - 1] == '/')
270  {
271  return http_resp_index_html(req, filepath);
272  }
273  // handle specifics and 404
274  if (stat(filepath, &file_stat) == -1)
275  {
276  /* If file not present on SPIFFS check if URI
277  * corresponds to one of the hardcoded paths */
278  if (strcmp(filename, "/index.html") == 0)
279  {
280  return index_html_get_handler(req);
281  }
282  if (strcmp(filename, "/overpass.otf") == 0)
283  {
284  return font_get_handler(req);
285  }
286  if (strcmp(filename, "/overpass-regular.otf") == 0)
287  {
288  return font_regular_get_handler(req);
289  }
290  // handle bundle js route
291  if (strcmp(filename, "/bundle.js") == 0)
292  {
293  return http_resp_bundle_js(req, filepath);
294  }
295  if (strcmp(filename, "/bundle.js.map") == 0)
296  {
297  return http_resp_bundle_js_map(req, filepath);
298  }
299  // handle bundle style route
300  if (strcmp(filename, "/bundle.css") == 0)
301  {
302  return http_resp_bundle_css(req, filepath);
303  }
304  // handle global style route
305  if (strcmp(filename, "/global.css") == 0)
306  {
307  return http_resp_global_css(req, filepath);
308  }
309  // handle robots
310  if (strcmp(filename, "/robots.txt") == 0)
311  {
312  return http_resp_robots_txt(req, filepath);
313  }
314  ESP_LOGE(TAG, "Failed to stat file : %s", filepath);
315  /* Respond with 404 Not Found */
316  httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "File does not exist");
317  return ESP_FAIL;
318  }
319 
320  fd = fopen(filepath, "r");
321  if (!fd)
322  {
323  ESP_LOGE(TAG, "Failed to read existing file : %s", filepath);
324  /* Respond with 500 Internal Server Error */
325  httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to read existing file");
326  return ESP_FAIL;
327  }
328 
329  ESP_LOGI(TAG, "Sending file : %s (%ld bytes)...", filename, file_stat.st_size);
330  set_content_type_from_file(req, filename);
331 
332  /* Retrieve the pointer to scratch buffer for temporary storage */
333  char *chunk = ((struct file_server_data *)req->user_ctx)->scratch;
334  size_t chunksize;
335  do
336  {
337  /* Read file in chunks into the scratch buffer */
338  chunksize = fread(chunk, 1, SCRATCH_BUFSIZE, fd);
339 
340  if (chunksize > 0)
341  {
342  /* Send the buffer contents as HTTP response chunk */
343  if (httpd_resp_send_chunk(req, chunk, chunksize) != ESP_OK)
344  {
345  fclose(fd);
346  ESP_LOGE(TAG, "File sending failed!");
347  /* Abort sending file */
348  httpd_resp_sendstr_chunk(req, NULL);
349  /* Respond with 500 Internal Server Error */
350  httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to send file");
351  return ESP_FAIL;
352  }
353  }
354 
355  /* Keep looping till the whole file is sent */
356  } while (chunksize != 0);
357 
358  /* Close file after sending complete */
359  fclose(fd);
360  ESP_LOGI(TAG, "File sending complete");
361 
362  /* Respond with an empty chunk to signal HTTP response completion */
363  httpd_resp_send_chunk(req, NULL, 0);
364  return ESP_OK;
365 }
366 
367 static esp_err_t api_post_handler(httpd_req_t *req)
368 {
369  ESP_LOGI(TAG, "post received at %s with size %d", req->uri, req->content_len);
370  char content[100];
371  size_t recv_size = MIN(req->content_len, sizeof(content));
372  int ret = httpd_req_recv(req, content, recv_size);
373  if (ret <= 0)
374  { /* 0 return value indicates connection closed */
375  /* Check if timeout occurred */
376  if (ret == HTTPD_SOCK_ERR_TIMEOUT)
377  {
378  /* In case of timeout one can choose to retry calling
379  * httpd_req_recv(), but to keep it simple, here we
380  * respond with an HTTP 408 (Request Timeout) error */
381  httpd_resp_send_408(req);
382  }
383  /* In case of error, returning ESP_FAIL will
384  * ensure that the underlying socket is closed */
385  return ESP_FAIL;
386  }
387 
388  ESP_LOGI(TAG, "post received with content: %s", content);
389 
390  if (strcmp(req->uri, "/api/set_temp") == 0)
391  {
392  goal = atof(content);
393  ESP_LOGI(TAG, "new goal as float: %0.2f", atof(content));
394  update_display();
395  }
396  else if (strcmp(req->uri, "/api/set_upper_margin") == 0)
397  {
398  over = atof(content);
399  ESP_LOGI(TAG, "new over as float: %0.2f", atof(content));
400  update_display();
401  }
402  else if (strcmp(req->uri, "/api/set_lower_margin") == 0)
403  {
404  under = atof(content);
405  ESP_LOGI(TAG, "new under as float: %0.2f", atof(content));
406  update_display();
407  }
408  else
409  {
410  ESP_LOGI(TAG, "post call to %s not handled by implemented checks, respond unsupported", req->uri);
411  httpd_resp_set_status(req, "501 Not Implemented");
412  httpd_resp_sendstr(req, "couldn't match that req to a server function");
413  }
414 
415  /* Send a simple response */
416  // const char resp[] = "URI POST Response";
417  // httpd_resp_send(req, resp, HTTPD_RESP_USE_STRLEN);
418  /* Redirect onto root to see the updated index */
419  httpd_resp_set_status(req, "303 See Other");
420  httpd_resp_set_hdr(req, "Location", "/");
421  httpd_resp_sendstr(req, "post processed successfully");
422  return ESP_OK;
423 }
429 esp_err_t start_file_server(const char *base_path)
430 {
431  static struct file_server_data *server_data = NULL;
432 
433  /* Validate file storage base path */
434  if (!base_path || strcmp(base_path, "/spiffs") != 0)
435  {
436  ESP_LOGE(TAG, "File server presently supports only '/spiffs' as base path");
437  return ESP_ERR_INVALID_ARG;
438  }
439 
440  if (server_data)
441  {
442  ESP_LOGE(TAG, "File server already started");
443  return ESP_ERR_INVALID_STATE;
444  }
445 
446  /* Allocate memory for server data */
447  server_data = calloc(1, sizeof(struct file_server_data));
448  if (!server_data)
449  {
450  ESP_LOGE(TAG, "Failed to allocate memory for server data");
451  return ESP_ERR_NO_MEM;
452  }
453  strlcpy(server_data->base_path, base_path,
454  sizeof(server_data->base_path));
455 
456  httpd_handle_t server = NULL;
457  httpd_config_t config = HTTPD_DEFAULT_CONFIG();
458 
459  /* Use the URI wildcard matching function in order to
460  * allow the same handler to respond to multiple different
461  * target URIs which match the wildcard scheme */
462  config.uri_match_fn = httpd_uri_match_wildcard;
463 
464  ESP_LOGI(TAG, "Starting HTTP Server");
465  if (httpd_start(&server, &config) != ESP_OK)
466  {
467  ESP_LOGE(TAG, "Failed to start file server!");
468  return ESP_FAIL;
469  }
470 
471  /* URI handler for getting uploaded files */
472  httpd_uri_t update = {
473  .uri = "/update",
474  .method = HTTP_GET,
475  .handler = update_get_handler,
476  .user_ctx = server_data // Pass server data as context
477  };
478  httpd_register_uri_handler(server, &update);
479 
480  /* URI handler for getting uploaded files */
481  httpd_uri_t file_download = {
482  .uri = "/*", // Match all URIs of type /path/to/file
483  .method = HTTP_GET,
484  .handler = download_get_handler,
485  .user_ctx = server_data // Pass server data as context
486  };
487  httpd_register_uri_handler(server, &file_download);
488 
489  /* URI handler for posts to api */
490  httpd_uri_t api_post = {
491  .uri = "/api/*", // Match all URIs of type /api/path
492  .method = HTTP_POST,
493  .handler = api_post_handler,
494  .user_ctx = server_data // Pass server data as context
495  };
496  httpd_register_uri_handler(server, &api_post);
497 
498  return ESP_OK;
499 }
char base_path[ESP_VFS_PATH_MAX+1]
Definition: webserver.c:62
char scratch[SCRATCH_BUFSIZE]
Definition: webserver.c:65
#define SCRATCH_BUFSIZE
Definition: webserver.c:57
esp_err_t start_file_server(const char *base_path)
Definition: webserver.c:429
#define FILE_PATH_MAX
Definition: webserver.c:49
off_t ws_get_file_size(const char *filename)
Definition: webserver.c:167
defininitions for webserver
volatile float temp
current temp
Definition: main.c:72
#define IS_FILE_EXT(filename, ext)
Definition: webserver.h:43
volatile float under
margin below goal temp at which to turn relay on
Definition: main.c:73
volatile float goal
goal temp for the system to aim for
Definition: main.c:71
volatile float over
margin above goal temp at which to turn relay off
Definition: main.c:74
void update_display(void)
Definition: main.c:86
const char * TAG
Definition: wifi_sta.c:42