main/task_http.c

branch
idf 5.1
changeset 142
1f7069278fe7
parent 137
e0f50087c909
equal deleted inserted replaced
141:e117e7462096 142:1f7069278fe7
1 /** 1 /**
2 * @file task_http.c 2 * @file task_http.c
3 * @brief HTTP and Websocket server functions. 3 * @brief HTTP and Websocket server functions.
4 * This uses some ESP32 Websocket code written by Blake Felt - blake.w.felt@gmail.com
5 */ 4 */
6 #include "config.h" 5 #include "config.h"
6 #include "sys/param.h"
7 #include "mbedtls/base64.h" 7 #include "mbedtls/base64.h"
8 #include "mbedtls/sha1.h" 8 #include "mbedtls/sha1.h"
9 #include "cJSON.h" 9 #include "cJSON.h"
10 10
11 11
12 static const char *TAG = "task_http"; 12 static const char *TAG = "task_http";
13 static QueueHandle_t client_queue; 13 httpd_handle_t web_server = NULL; ///< The http server handle.
14 const static int client_queue_size = 10;
15 static TaskHandle_t xTaskHTTP = NULL;
16 static TaskHandle_t xTaskQueue = NULL;
17 14
18 cJSON *root = NULL; 15 cJSON *root = NULL;
19 cJSON *touch = NULL; 16 cJSON *touch = NULL;
17
20 18
21 typedef struct _ls_list { 19 typedef struct _ls_list {
22 struct _ls_list *next; 20 struct _ls_list *next;
23 char d_name[64]; 21 char d_name[64];
24 off_t size; 22 off_t size;
25 long mtime; 23 long mtime;
26 } ls_list; 24 } ls_list;
27 25
28 26
29 27 /**
28 * @brief Clear the linked list and release memory.
29 */
30 void tidy_lslist(ls_list **lap) 30 void tidy_lslist(ls_list **lap)
31 { 31 {
32 ls_list *tmp, *old; 32 ls_list *tmp, *old;
33 33
34 for (tmp = *lap; tmp; tmp = old) { 34 for (tmp = *lap; tmp; tmp = old) {
37 } 37 }
38 *lap = NULL; 38 *lap = NULL;
39 } 39 }
40 40
41 41
42 42 /**
43 * @brief Add a file entry to the linked list.
44 * @param lap Pointer to the linked list.
45 * @param name The entry filename.
46 * @param size The entry filesize.
47 * @param mtime The entry filedate.
48 */
43 void fill_list(ls_list **lap, char *name, off_t size, long mtime) 49 void fill_list(ls_list **lap, char *name, off_t size, long mtime)
44 { 50 {
45 ls_list **tmp; 51 ls_list **tmp;
46 52
47 for (tmp = lap; *tmp; tmp = &((*tmp)->next)); 53 for (tmp = lap; *tmp; tmp = &((*tmp)->next));
53 (*tmp)->mtime = mtime; 59 (*tmp)->mtime = mtime;
54 tmp = &((*tmp)->next); 60 tmp = &((*tmp)->next);
55 } 61 }
56 62
57 63
58 64 /**
65 * @brief Compare for sorting files by datetime, reversed.
66 * @param lsp1 Entry 1
67 * @param lsp2 Entry 2
68 */
59 int comp(ls_list **lsp1, ls_list **lsp2) 69 int comp(ls_list **lsp1, ls_list **lsp2)
60 { 70 {
61 char as[20], bs[20]; 71 char as[20], bs[20];
62 72
63 snprintf(as, 19, "%ld", (*lsp1)->mtime); 73 snprintf(as, 19, "%ld", (*lsp1)->mtime);
65 // Reverse order 75 // Reverse order
66 return strcmp(bs, as); 76 return strcmp(bs, as);
67 } 77 }
68 78
69 79
70 80 /**
81 * @brief Sort the linked list.
82 */
71 void sort_list(ls_list **lap) 83 void sort_list(ls_list **lap)
72 { 84 {
73 ls_list *ta, **vector; 85 ls_list *ta, **vector;
74 size_t n = 0, i; 86 size_t n = 0, i;
75 87
99 free(vector); 111 free(vector);
100 return; 112 return;
101 } 113 }
102 114
103 115
104 116 #define CHECK_FILE_EXTENSION(filename, ext) (strcasecmp(&filename[strlen(filename) - strlen(ext)], ext) == 0)
105 /** 117
106 * @brief Debug dump buffer 118 /**
107 * @param buf The buffer 119 * @brief Set HTTP response content type according to file extension
108 * @param buflen Length of the buffer 120 * @param req The request where the content type will be set.
109 */ 121 * @param filepath The filename to get the extension from.
110 #if 0 122 * @return ESP_OK if success.
111 void dump_buf(char *buf, uint16_t buflen); 123 */
112 #endif 124 static esp_err_t set_content_type_from_file(httpd_req_t *req, const char *filepath)
113 125 {
114 126 const char *type = "text/plain";
115 /** 127 if (CHECK_FILE_EXTENSION(filepath, ".html")) {
116 * @brief Send HTTP error and message 128 type = "text/html";
117 * @param conn The socket to send to. 129 } else if (CHECK_FILE_EXTENSION(filepath, ".log")) {
118 * @param error The HTTP error code. 130 type = "text/plain";
119 * @param message Yhe human readable explanation. 131 } else if (CHECK_FILE_EXTENSION(filepath, ".js")) {
120 * @paeam body The human readable explanation. 132 type = "application/javascript";
121 */ 133 } else if (CHECK_FILE_EXTENSION(filepath, ".json")) {
122 static void http_error(struct netconn *conn, int error, char *message, char *body) 134 type = "text/json";
123 { 135 } else if (CHECK_FILE_EXTENSION(filepath, ".gz")) {
124 char *tmp = malloc(200); 136 type = "application/x-gzip";
125 137 } else if (CHECK_FILE_EXTENSION(filepath, ".css")) {
126 ESP_LOGI(TAG, "Http response %d - %s", error, message); 138 type = "text/css";
127 if (strlen(body)) { 139 } else if (CHECK_FILE_EXTENSION(filepath, ".png")) {
128 snprintf(tmp, 199, "HTTP/1.1 %d %s\r\nContent-type: text/plain\r\n\r\n%s\n", error, message, body); 140 type = "image/png";
129 } else { 141 } else if (CHECK_FILE_EXTENSION(filepath, ".ico")) {
130 snprintf(tmp, 199, "HTTP/1.1 %d %s\r\n\r\n", error, message); 142 type = "image/x-icon";
131 } 143 } else if (CHECK_FILE_EXTENSION(filepath, ".svg")) {
132 netconn_write(conn, tmp, strlen(tmp), NETCONN_NOCOPY); 144 type = "text/xml";
133 free(tmp); 145 } else if (CHECK_FILE_EXTENSION(filepath, ".ttf")) {
134 } 146 type = "font/ttf";
135 147 } else if (CHECK_FILE_EXTENSION(filepath, ".woff")) {
136 148 type = "font/woff";
137 149 } else if (CHECK_FILE_EXTENSION(filepath, ".woff2")) {
138 /** 150 type = "font/woff2";
139 * @brief Send HTTP file from the filesystem. 151 } else if (CHECK_FILE_EXTENSION(filepath, ".xml")) {
140 * @param conn The network connection. 152 type = "text/xml";
141 * @param url The requested url. 153 }
142 * @param mod_since The date/time of the file, or NULL. 154 return httpd_resp_set_type(req, type);
143 * @param ipstr The IP address of the remote. 155 }
144 */ 156
145 static void http_sendfile(struct netconn *conn, char *url, char *mod_since, char *ipstr) 157
146 { 158 /**
147 char temp_url[128], temp_url_gz[132], header[256], c_type[32]; 159 * @brief Send text message to a single websocket client.
160 * @param num The client socket to send to.
161 * @param msg The message to send.
162 * @return ESP_OK.
163 */
164 int ws_server_send_text_client(int num, char* msg)
165 {
166 httpd_ws_frame_t ws_pkt;
167
168 memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
169 ws_pkt.type = HTTPD_WS_TYPE_TEXT;
170 ws_pkt.len = strlen(msg);
171 ws_pkt.payload = (uint8_t*)msg;
172 httpd_ws_send_frame_async(web_server, num, &ws_pkt);
173 ESP_LOGD(TAG, "ws_server_send_text_client(%d, %s)", num, msg);
174 return ESP_OK;
175 }
176
177
178 /**
179 * @brief Broadcast text message to all connected websocket clients.
180 * @param msg The text message.
181 * @return -1 if error, else the number of clients.
182 */
183 int ws_server_send_text_clients(char* msg)
184 {
185 httpd_ws_frame_t ws_pkt;
186 static size_t max_clients = CONFIG_LWIP_MAX_LISTENING_TCP;
187 size_t fds = max_clients;
188 int client_fds[CONFIG_LWIP_MAX_LISTENING_TCP] = {0};
189 int count = 0;
190
191 memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
192 ws_pkt.type = HTTPD_WS_TYPE_TEXT;
193 ws_pkt.len = strlen(msg);
194 ws_pkt.payload = (uint8_t*)msg;
195
196 esp_err_t ret = httpd_get_client_list(web_server, &fds, client_fds);
197 if (ret != ESP_OK) {
198 return -1;
199 }
200
201 for (int i = 0; i < fds; i++) {
202 httpd_ws_client_info_t client_info = httpd_ws_get_fd_info(web_server, client_fds[i]);
203 if (client_info == HTTPD_WS_CLIENT_WEBSOCKET) {
204 httpd_ws_send_frame_async(web_server, client_fds[i], &ws_pkt);
205 count++;
206 }
207 }
208 return count;
209 }
210
211
212 /**
213 * @brief Websocket handler.
214 * @param req The request structure.
215 * @return ESP_OK or error.
216 */
217 static esp_err_t ws_handler(httpd_req_t *req)
218 {
219 if (req->method == HTTP_GET) {
220 ESP_LOGI(TAG, "New websocket connection %d", httpd_req_to_sockfd(req));
221 /* Send initial screen */
222 TFTstartWS(httpd_req_to_sockfd(req));
223 return ESP_OK;
224 }
225
226 httpd_ws_frame_t ws_pkt;
227 uint8_t *buf = NULL;
228 char jbuf[128];
229 cJSON *root = NULL, *touch = NULL;
230
231 memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
232 ws_pkt.type = HTTPD_WS_TYPE_TEXT;
233 /* Set max_len = 0 to get the frame len */
234 esp_err_t ret = httpd_ws_recv_frame(req, &ws_pkt, 0);
235 if (ret != ESP_OK) {
236 ESP_LOGE(TAG, "ws_handler() httpd_ws_recv_frame failed to get frame len with %d", ret);
237 return ret;
238 }
239 ESP_LOGD(TAG, "frame len is %d type is %d socket %d", ws_pkt.len, ws_pkt.type, httpd_req_to_sockfd(req));
240
241 if (ws_pkt.len) {
242 /* ws_pkt.len + 1 is for NULL termination as we are expecting a string */
243 buf = calloc(1, ws_pkt.len + 1);
244 if (buf == NULL) {
245 ESP_LOGE(TAG, "ws_handler() no memory for buf");
246 return ESP_ERR_NO_MEM;
247 }
248 ws_pkt.payload = buf;
249 /* Set max_len = ws_pkt.len to get the frame payload */
250 ret = httpd_ws_recv_frame(req, &ws_pkt, ws_pkt.len);
251 if (ret != ESP_OK) {
252 ESP_LOGE(TAG, "ws_handler() httpd_ws_recv_frame failed with %d", ret);
253 free(buf);
254 return ret;
255 }
256 ESP_LOGI(TAG, "ws_handler() Got message: %s", ws_pkt.payload);
257 }
258
259 if ((ws_pkt.type == HTTPD_WS_TYPE_TEXT) && (ws_pkt.len > 0) && (ws_pkt.len < 128)) {
260 memcpy(jbuf, ws_pkt.payload, ws_pkt.len);
261 jbuf[ws_pkt.len] = '\0';
262 if ((root = cJSON_Parse(jbuf))) {
263 if ((touch = cJSON_GetObjectItem(root,"touch"))) {
264 int x = cJSON_GetObjectItem(touch, "x")->valueint;
265 int y = cJSON_GetObjectItem(touch, "y")->valueint;
266 WS_touched(x, y);
267 } else {
268 ESP_LOGI(TAG,"not json touch");
269 }
270 cJSON_Delete(root);
271 } else {
272 ESP_LOGI(TAG,"not json");
273 }
274 }
275
276 free(buf);
277 return ESP_OK;
278 }
279
280
281 /**
282 * @brief Handle files download from /spiffs or /sdcard.
283 */
284 static esp_err_t files_handler(httpd_req_t *req)
285 {
286 char temp_url[560], temp_url_gz[564];
148 struct stat st; 287 struct stat st;
149 off_t filesize; 288 off_t filesize;
150 size_t sentsize;
151 char strftime_buf[64]; 289 char strftime_buf[64];
152 err_t err;
153 bool send_gz; 290 bool send_gz;
154 FILE *f; 291 int fd = -1;
155 292
156 if (url[strlen(url) - 1] == '/') { 293 if (req->uri[strlen(req->uri) - 1] == '/') {
157 // If no filename given, use index.html 294 // If no filename given, use index.html
158 sprintf(temp_url, "/spiffs/w%sindex.html", url); 295 sprintf(temp_url, "/spiffs/w%sindex.html", req->uri);
159 } else if (strncmp(url, "/log/", 5) == 0) { 296 } else if (strncmp(req->uri, "/log/", 5) == 0) {
160 // Logfiles are on the SD card. 297 // Logfiles are on the SD card.
161 sprintf(temp_url, "/sdcard/w%s", url); 298 sprintf(temp_url, "/sdcard/w%s", req->uri);
162 } else { 299 } else {
163 sprintf(temp_url, "/spiffs/w%s", url); 300 sprintf(temp_url, "/spiffs/w%s", req->uri);
164 for (int i = 0; i < 127; i++) { 301 for (int i = 0; i < 127; i++) {
165 if (temp_url[i] == '?') 302 if (temp_url[i] == '?')
166 temp_url[i] = '\0'; 303 temp_url[i] = '\0';
167 if (temp_url[i] == '\0') 304 if (temp_url[i] == '\0')
168 break; 305 break;
169 } 306 }
170 } 307 }
171 snprintf(temp_url_gz, 131, "%s.gz", temp_url); 308 snprintf(temp_url_gz, 563, "%s.gz", temp_url);
172 309
173 /* 310 /*
174 * Get filesize and date for the response headers. 311 * Get filesize and date for the response headers.
175 * Note that the /spiffs filesystem doesn't support file timestamps 312 * Note that the /spiffs filesystem doesn't support file timestamps
176 * and returns the epoch for each file. Therefore we cannot use 313 * and returns the epoch for each file. Therefore we cannot use
178 */ 315 */
179 filesize = 0; 316 filesize = 0;
180 strftime_buf[0] = '\0'; 317 strftime_buf[0] = '\0';
181 send_gz = false; 318 send_gz = false;
182 if (stat(temp_url_gz, &st) == 0) { 319 if (stat(temp_url_gz, &st) == 0) {
320 /* The requested file is gzipped. */
183 filesize = st.st_size; 321 filesize = st.st_size;
184 strftime(strftime_buf, sizeof(strftime_buf), "%a, %d %b %Y %T %z", localtime(&(st.st_mtime))); 322 strftime(strftime_buf, sizeof(strftime_buf), "%a, %d %b %Y %T %z", localtime(&(st.st_mtime)));
185 send_gz = true; 323 send_gz = true;
186 } else if (stat(temp_url, &st) == 0) { 324 } else if (stat(temp_url, &st) == 0) {
187 filesize = st.st_size; 325 filesize = st.st_size;
188 strftime(strftime_buf, sizeof(strftime_buf), "%a, %d %b %Y %T %z", localtime(&(st.st_mtime))); 326 strftime(strftime_buf, sizeof(strftime_buf), "%a, %d %b %Y %T %z", localtime(&(st.st_mtime)));
189 } 327 }
328 ESP_LOGI(TAG, "GET `%s` file %s date %s size %d", req->uri, (send_gz) ? temp_url_gz : temp_url, strftime_buf, (int)filesize);
329
330 if (send_gz) {
331 fd = open(temp_url_gz, O_RDONLY, 0);
332 } else {
333 fd = open(temp_url, O_RDONLY, 0);
334 }
335 if (fd == -1) {
336 ESP_LOGI(TAG, "files_handler() file not found");
337 httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "File does not exist");
338 return ESP_FAIL;
339 }
340
341 set_content_type_from_file(req, temp_url);
342 if (send_gz) {
343 httpd_resp_set_hdr(req, "Content-Encoding", "gzip");
344 }
190 345
191 /* 346 /*
192 * If we have a If-Modified-Since parameter, compare that with the current 347 * Now the real file send in chunks.
193 * filedate on disk. If It's the same send a 304 response.
194 * Cannot work on /spiffs.
195 * Update 18-05-2021 it seems better on idf 4.2.1 but still not correct.
196 */ 348 */
197 #if 0 349 char *chunk = malloc(1024);
198 time_t Now; 350 ssize_t read_bytes;
199 struct tm timeInfo; 351 do {
200 if (mod_since && strlen(strftime_buf)) { 352 read_bytes = read(fd, chunk, 1024);
201 time(&Now); 353 if (read_bytes == -1) {
202 localtime_r(&Now, &timeInfo); 354 ESP_LOGE(TAG, "files_handler() Failed to read file : %s", (send_gz) ? temp_url_gz : temp_url);
203 strftime(strftime_buf, sizeof(strftime_buf), "%a, %d %b %Y %T %z", &timeInfo); 355 } else if (read_bytes > 0) {
204 sprintf(header, "HTTP/1.1 304 Not Modified\r\nDate: %s\r\n\r\n", strftime_buf); 356 /* Send the buffer contents as HTTP response chunk */
205 netconn_write(conn, header, strlen(header), NETCONN_NOCOPY); 357 if (httpd_resp_send_chunk(req, chunk, read_bytes) != ESP_OK) {
206 ESP_LOGI(TAG, "%s sendfile %s Not Modified, ok", ipstr, temp_url); 358 close(fd);
207 return; 359 ESP_LOGE(TAG, "files_handler() File sending failed!");
208 } 360 /* Abort sending file */
209 #endif 361 httpd_resp_sendstr_chunk(req, NULL);
210 362 /* Respond with 500 Internal Server Error */
211 if (send_gz) { 363 httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to send file");
212 f = fopen(temp_url_gz, "r"); 364 return ESP_FAIL;
365 }
366 }
367 } while (read_bytes > 0);
368 /* Close file after sending complete */
369 close(fd);
370
371 /* Respond with an empty chunk to signal HTTP response completion */
372 httpd_resp_send_chunk(req, NULL, 0);
373 return ESP_OK;
374 }
375
376
377 /**
378 * @brief Handle 'GET /logfiles.json'
379 */
380 static esp_err_t logfiles_handler(httpd_req_t *req)
381 {
382 ls_list *lsx = NULL, *tmp;
383 char temp[64];
384 FILE *dest = fopen("/spiffs/w/logfiles.json", "w");
385
386 if (dest) {
387 DIR *dir = opendir("/sdcard/w/log");
388 if (dir) {
389 struct dirent* de = readdir(dir);
390 struct stat st;
391 while (de) {
392 snprintf(temp, 63, "/sdcard/w/log/");
393 strncat(temp, de->d_name, 63 - strlen(temp));
394 if (stat(temp, &st) == ESP_OK) {
395 fill_list(&lsx, de->d_name, st.st_size, st.st_mtime);
396 }
397 de = readdir(dir);
398 vTaskDelay(5 / portTICK_PERIOD_MS);
399 }
400 closedir(dir);
401 } else {
402 ESP_LOGE(TAG, "Error %d open directory /sdcard/w/log", errno);
403 }
404
405 sort_list(&lsx);
406 bool comma = false;
407 fprintf(dest, "{\"Dir\":[{\"Folder\":\"/log\",\"Files\":[");
408 for (tmp = lsx; tmp; tmp = tmp->next) {
409 fprintf(dest, "%s{\"File\":\"%s\",\"Size\":%ld,\"Date\":%ld}", (comma)?",":"", tmp->d_name, tmp->size, tmp->mtime);
410 comma = true;
411 }
412 fprintf(dest, "]}]}");
413 fclose(dest);
414 tidy_lslist(&lsx);
213 } else { 415 } else {
214 f = fopen(temp_url, "r"); 416 ESP_LOGE(TAG, "Error %d write /spiffs/w/logfiles.json", errno);
215 } 417 httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to create file");
216 if (f == NULL) { 418 return ESP_FAIL;
217 ESP_LOGI(TAG, "%s url \'%s\' file \'%s\' not found", ipstr, url, temp_url); 419 }
218 http_error(conn, 404, (char *)"Not found", (char *)"Not found"); 420
219 return; 421 /*
220 } 422 * File is ready, send it using the files_handler().
221 423 */
222 if (strcmp(".html", &temp_url[strlen(temp_url) - 5]) == 0) { 424 esp_err_t ret = files_handler(req);
223 sprintf(c_type, "text/html"); 425 unlink("/spiffs/w/logfiles.json");
224 } else if (strcmp(".css", &temp_url[strlen(temp_url) - 4]) == 0) { 426 return ret;
225 sprintf(c_type, "text/css"); 427 }
226 } else if (strcmp(".log", &temp_url[strlen(temp_url) - 4]) == 0) { 428
227 sprintf(c_type, "text/plain"); 429
228 } else if (strcmp(".js", &temp_url[strlen(temp_url) - 3]) == 0) { 430 esp_err_t start_webserver(void)
229 sprintf(c_type, "text/javascript"); 431 {
230 } else if (strcmp(".json", &temp_url[strlen(temp_url) - 5]) == 0) { 432 httpd_config_t config = HTTPD_DEFAULT_CONFIG();
231 sprintf(c_type, "text/json"); 433 config.lru_purge_enable = true;
232 } else if (strcmp(".gz", &temp_url[strlen(temp_url) - 3]) == 0) { 434 config.uri_match_fn = httpd_uri_match_wildcard;
233 sprintf(c_type, "application/x-gzip"); 435
234 } else if (strcmp(".png", &temp_url[strlen(temp_url) - 4]) == 0) { 436 // Start the httpd server
235 sprintf(c_type, "image/png"); 437 if (web_server == NULL) {
236 } else if (strcmp(".svg", &temp_url[strlen(temp_url) - 4]) == 0) { 438 ESP_LOGI(TAG, "Starting httpd on port: '%d'", config.server_port);
237 sprintf(c_type, "image/svg+xml"); 439 if (httpd_start(&web_server, &config) == ESP_OK) {
238 } else if (strcmp(".oga", &temp_url[strlen(temp_url) - 4]) == 0) { 440 // Set URI handlers
239 sprintf(c_type, "audio/ogg"); 441 ESP_LOGD(TAG, "Registering URI handlers");
240 } else if (strcmp(".ico", &temp_url[strlen(temp_url) - 4]) == 0) { 442
241 sprintf(c_type, "image/x-icon"); 443 httpd_uri_t ws = {
242 } else if (strcmp(".ttf", &temp_url[strlen(temp_url) - 4]) == 0) { 444 .uri = "/ws",
243 sprintf(c_type, "font/ttf"); 445 .method = HTTP_GET,
244 } else if (strcmp(".woff", &temp_url[strlen(temp_url) - 5]) == 0) { 446 .handler = ws_handler,
245 sprintf(c_type, "font/woff"); 447 .user_ctx = NULL,
246 } else if (strcmp(".woff2", &temp_url[strlen(temp_url) - 6]) == 0) { 448 .is_websocket = true
247 sprintf(c_type, "font/woff2"); 449 };
248 } else if (strcmp(".xml", &temp_url[strlen(temp_url) - 4]) == 0) { 450 httpd_register_uri_handler(web_server, &ws);
249 sprintf(c_type, "text/xml"); 451
452 httpd_uri_t logfiles = {
453 .uri = "/logfiles.json",
454 .method = HTTP_GET,
455 .handler = logfiles_handler,
456 .user_ctx = NULL
457 };
458 httpd_register_uri_handler(web_server, &logfiles);
459
460 httpd_uri_t vfs_files = {
461 .uri = "/*",
462 .method = HTTP_GET,
463 .handler = files_handler,
464 .user_ctx = NULL
465 };
466 httpd_register_uri_handler(web_server, &vfs_files);
467 return ESP_OK;
468 }
250 } else { 469 } else {
251 sprintf(c_type, "application/octet-stream"); 470 ESP_LOGI(TAG, "httpd server already started");
252 printf("Unknown content type for %s\n", temp_url); 471 return ESP_OK;
253 } 472 }
254 473
255 vTaskDelay(2 / portTICK_PERIOD_MS); 474 ESP_LOGI(TAG, "Error starting server!");
256 // httpdHeader(connData, "Cache-Control", "max-age=3600, must-revalidate"); 475 return ESP_FAIL;
257 if (filesize) { 476 }
258 sprintf(header, "HTTP/1.1 200 OK\r\nLast-Modified: %s\r\nContent-Length: %ld\r\nContent-type: %s\r\n", strftime_buf, filesize, c_type); 477
259 } else { 478
260 sprintf(header, "HTTP/1.1 200 OK\r\nContent-type: %s\r\n", c_type); 479 static esp_err_t stop_webserver(void)
261 } 480 {
262 if (send_gz) { 481 esp_err_t ret;
263 strncat(header, "Content-Encoding: gzip\r\n", 255 - strlen(header)); 482
264 } 483 ESP_LOGI(TAG, "Stopping httpd server");
265 strncat(header, "\r\n", 255 - strlen(header)); // Add last empty line. 484 ret = httpd_stop(web_server);
266 err = netconn_write(conn, header, strlen(header), NETCONN_NOCOPY); 485 web_server = NULL;
267 if (err != ERR_OK) { 486 return ret;
268 ESP_LOGW(TAG, "%s sendfile %s%s err=%d on header write", ipstr, temp_url, (send_gz) ? ".gz":"", err); 487 }
269 fclose(f); 488
270 return; 489
271 } 490 static void disconnect_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data)
272 // if (strstr(acceptEncodingBuffer, "gzip") == NULL) 491 {
273 // http_error(conn, 501, "Not implemented", "Your browser does not accept gzip-compressed data."); 492 if (web_server) {
274 493 if (stop_webserver() == ESP_OK) {
275 sentsize = 0; 494 web_server = NULL;
276 uint8_t *buff = malloc(1024); 495 } else {
277 size_t bytes; 496 ESP_LOGE(TAG, "Failed to stop http server");
278 int pause = 0; 497 }
279 498 }
280 for (;;) { 499 }
281 bytes = fread(buff, 1, 1024, f); 500
282 if (bytes == 0) 501
283 break; 502 static void connect_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data)
284 503 {
285 err = netconn_write(conn, buff, bytes, NETCONN_NOCOPY); 504 start_webserver();
286 if (err != ERR_OK) { 505 }
287 ESP_LOGW(TAG, "%s sendfile %s%s err=%d send %u bytes of %ld bytes", ipstr, temp_url, (send_gz) ? ".gz":"", err, sentsize, filesize); 506
288 break; 507
289 } 508 /**
290 vTaskDelay(2 / portTICK_PERIOD_MS); 509 * @brief Install the HTTP server event handlers.
291 sentsize += bytes; 510 * The real server is started and stopped on events.
292 pause++; 511 */
293 if (pause > 50) { // 50 K 512 void install_http_server(void)
294 pause = 0; 513 {
295 vTaskDelay(50 / portTICK_PERIOD_MS); 514 ESP_LOGI(TAG, "Install HTTP server");
296 } 515
297 } 516 /*
298 fclose(f); 517 * Respond to WiFi and network events. This is much better then letting the
299 free(buff); 518 * server run forever.
300 519 */
301 if (sentsize == filesize) { 520 ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &connect_handler, web_server));
302 ESP_LOGI(TAG, "%s sendfile %s%s sent %u bytes, ok (%s)", ipstr, temp_url, (send_gz) ? ".gz":"", sentsize, url); 521 ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_LOST_IP, &disconnect_handler, web_server));
303 } 522 ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, &disconnect_handler, web_server));
304 } 523 }
305 524
306 525
307
308 /**
309 * @brief Handle web ui websocket events.
310 */
311 void websock_callback(uint8_t num, WEBSOCKET_TYPE_t type, char* msg, uint64_t len)
312 {
313 char jbuf[128];
314
315 switch(type) {
316 case WEBSOCKET_CONNECT:
317 case WEBSOCKET_DISCONNECT_EXTERNAL:
318 break;
319
320 case WEBSOCKET_DISCONNECT_INTERNAL:
321 ESP_LOGI(TAG,"Websocket client %i was disconnected",num);
322 break;
323
324 case WEBSOCKET_DISCONNECT_ERROR:
325 ESP_LOGI(TAG,"Websocket client %i was disconnected due to an error",num);
326 break;
327
328 case WEBSOCKET_TEXT:
329 /*
330 * Handle json actions from the web clients, like button presses.
331 */
332 if (len < 128) { // Safety, messages are small.
333 memcpy(jbuf, msg, len);
334 jbuf[len] = '\0';
335 if ((root = cJSON_Parse(jbuf))) {
336 if ((touch = cJSON_GetObjectItem(root,"touch"))) {
337 int x = cJSON_GetObjectItem(touch, "x")->valueint;
338 int y = cJSON_GetObjectItem(touch, "y")->valueint;
339 WS_touched(x, y);
340 break;
341 } else {
342 ESP_LOGI(TAG,"not json touch");
343 }
344 cJSON_Delete(root);
345 } else {
346 ESP_LOGI(TAG,"not json");
347 }
348 }
349 // Log if the message in not processed.
350 ESP_LOGI(TAG,"Websocket client %i sent text message of size %i:\n%s",num,(unsigned)len,msg);
351 break;
352
353 case WEBSOCKET_BIN:
354 ESP_LOGI(TAG,"Websocket client %i sent bin message of size %i:\n",num,(unsigned)len);
355 break;
356
357 case WEBSOCKET_PING:
358 ESP_LOGI(TAG,"client %i pinged us with message of size %i:\n%s",num,(unsigned)len,msg);
359 break;
360
361 case WEBSOCKET_PONG:
362 ESP_LOGI(TAG,"client %i responded to the ping",num);
363 break;
364 }
365 }
366
367
368
369 #if 0
370 void dump_buf(char *buf, uint16_t buflen)
371 {
372 int i = 0, l = 1;
373
374 printf("request length %d\n00: ", buflen);
375 for (;;) {
376 if (i >= buflen)
377 break;
378 if ((buf[i] < ' ') || (buf[i] > 126)) {
379 if (buf[i] == '\n') {
380 printf("\\n\n%02d: ", l);
381 l++;
382 } else if (buf[i] == '\r') {
383 printf("\\r");
384 } else {
385 printf("\\%02x", buf[i]);
386 }
387 } else {
388 printf("%c", buf[i]);
389 }
390 i++;
391 }
392 printf("\n");
393 }
394 #endif
395
396
397
398 /**
399 * @brief Serve any client.
400 * @param http_client_sock The socket on which the client is connected.
401 */
402 static void http_serve(struct netconn *conn)
403 {
404 char ipstr[IPADDR_STRLEN_MAX];
405 struct netbuf *inbuf;
406 static char *buf;
407 static uint16_t buflen;
408 static err_t err;
409 char url[128], *p, *mod_since = NULL;
410 ip_addr_t remote_addr;
411 uint16_t remote_port;
412 ls_list *lsx = NULL, *tmp;
413
414 if (netconn_getaddr(conn, &remote_addr, &remote_port, 0) == ERR_OK) {
415 strcpy(ipstr, ip4addr_ntoa(ip_2_ip4(&remote_addr)));
416 } else {
417 ipstr[0] = '\0';
418 }
419
420 netconn_set_recvtimeout(conn,1000); // allow a connection timeout of 1 second
421 err = netconn_recv(conn, &inbuf);
422
423 if (err != ERR_OK) {
424 if (err != ERR_TIMEOUT) { // Ignore timeout
425 ESP_LOGW(TAG,"%s error %d on read", ipstr, err);
426 }
427 netconn_close(conn);
428 netconn_delete(conn);
429 netbuf_delete(inbuf);
430 return;
431 }
432
433 netbuf_data(inbuf, (void**)&buf, &buflen);
434
435 if (buf) {
436 /*
437 * Build url string from the data buffer.
438 * It looks like: GET /app/localization.js HTTP/1.1
439 */
440 for (int i = 0; i < 10; i++) {
441 if (buf[i] == ' ') {
442 i++;
443 for (int j = i; j < 128; j++) {
444 url[j-i] = buf[j];
445 if (url[j-i] == ' ') {
446 url[j-i] = '\0';
447 break;
448 }
449 }
450 break;
451 }
452 }
453
454 if (strstr(buf, "GET /logfiles.json")) {
455 char temp[64];
456 FILE *dest = fopen("/spiffs/w/logfiles.json", "w");
457 if (dest) {
458 DIR *dir = opendir("/sdcard/w/log");
459 if (dir) {
460 struct dirent* de = readdir(dir);
461 struct stat st;
462 while (de) {
463 snprintf(temp, 63, "/sdcard/w/log/");
464 strncat(temp, de->d_name, 63 - strlen(temp));
465 if (stat(temp, &st) == ESP_OK) {
466 fill_list(&lsx, de->d_name, st.st_size, st.st_mtime);
467 }
468 de = readdir(dir);
469 vTaskDelay(5 / portTICK_PERIOD_MS);
470 }
471 closedir(dir);
472 } else {
473 ESP_LOGE(TAG, "Error %d open directory /sdcard/w/log", errno);
474 }
475
476 sort_list(&lsx);
477 bool comma = false;
478 fprintf(dest, "{\"Dir\":[{\"Folder\":\"/log\",\"Files\":[");
479 for (tmp = lsx; tmp; tmp = tmp->next) {
480 fprintf(dest, "%s{\"File\":\"%s\",\"Size\":%ld,\"Date\":%ld}", (comma)?",":"", tmp->d_name, tmp->size, tmp->mtime);
481 comma = true;
482 }
483 fprintf(dest, "]}]}");
484 fclose(dest);
485 tidy_lslist(&lsx);
486 } else {
487 ESP_LOGE(TAG, "Error %d write /spiffs/w/logfiles.json", errno);
488 }
489 http_sendfile(conn, (char *)"/logfiles.json", NULL, ipstr);
490 netconn_close(conn);
491 netconn_delete(conn);
492 netbuf_delete(inbuf);
493 unlink("/spiffs/w/logfiles.json");
494 return;
495 }
496
497 // http requests
498 if (! strstr(buf,"Upgrade: websocket")) { // Not websocket
499 p = strstr(buf, "If-Modified-Since:"); // May be cached
500 if (p) {
501 size_t mod_len = strcspn(p, " ");
502 p += (int)(mod_len + 1);
503 mod_len = strcspn(p, "\r\n");
504 mod_since = malloc(mod_len + 2);
505 memcpy(mod_since, p, mod_len);
506 mod_since[mod_len] = '\0';
507 }
508 http_sendfile(conn, url, mod_since, ipstr);
509 if (mod_since)
510 free(mod_since);
511 mod_since = NULL;
512 netconn_close(conn);
513 netconn_delete(conn);
514 netbuf_delete(inbuf);
515 return;
516 }
517
518 // websocket for web UI.
519 if ((strstr(buf,"GET /ws ") && strstr(buf,"Upgrade: websocket"))) {
520 int nr = ws_server_add_client_protocol(conn, buf, buflen, (char *)"/ws", (char *)"binary", websock_callback);
521 ESP_LOGI(TAG, "%s new websocket on /ws client: %d", ipstr, nr);
522 netbuf_delete(inbuf);
523 TFTstartWS(nr);
524 // Startup something? Init webscreen?
525 return;
526 }
527
528 #if 0
529 dump_buf(buf, buflen);
530 #endif
531
532 if (strstr(buf, "GET /")) {
533 ESP_LOGI(TAG, "%s request: %s", ipstr, buf);
534 http_error(conn, 404, (char *)"Not found", (char *)"Not found");
535 } else {
536 http_error(conn, 405, (char *)"Invalid method", (char *)"Invalid method");
537 }
538 }
539
540 netconn_close(conn);
541 netconn_delete(conn);
542 netbuf_delete(inbuf);
543 }
544
545
546
547 /**
548 * @brief Handles clients when they first connect. passes to a queue
549 */
550 static void task_HTTPserver(void* pvParameters)
551 {
552 struct netconn *conn, *newconn;
553 static err_t err;
554
555 ESP_LOGI(TAG, "Start HTTP/web server");
556
557 conn = netconn_new(NETCONN_TCP);
558 netconn_bind(conn,NULL,80);
559 netconn_listen(conn);
560
561 do {
562 err = netconn_accept(conn, &newconn);
563 if (err == ERR_OK) {
564 if (xQueueSendToBack(client_queue,&newconn,portMAX_DELAY) != pdTRUE) {
565 ESP_LOGW(TAG, "xQueueSendToBack() queue full");
566 };
567 }
568 vTaskDelay(5 / portTICK_PERIOD_MS);
569 } while (err == ERR_OK);
570
571 ESP_LOGE(TAG, "Stopping HTTP/web server");
572 netconn_close(conn);
573 netconn_delete(conn);
574 vTaskDelete(NULL);
575 }
576
577
578
579 /**
580 * @brief Receives clients from queue and handle them.
581 */
582 static void task_Queue(void* pvParameters)
583 {
584 struct netconn* conn;
585
586 ESP_LOGI(TAG, "Start Queue task");
587 for(;;) {
588 xQueueReceive(client_queue, &(conn), portMAX_DELAY);
589 if (!conn)
590 continue;
591 http_serve(conn);
592 vTaskDelay(2 / portTICK_PERIOD_MS);
593 }
594 ESP_LOGE(TAG, "Stopping Queue task");
595 vTaskDelete(NULL);
596 }
597
598
599
600 void start_http_websocket(void)
601 {
602 ESP_LOGI(TAG, "Start HTTP/Websocket server");
603
604 client_queue = xQueueCreate(client_queue_size,sizeof(struct netconn*));
605 if (client_queue == 0) {
606 ESP_LOGE(TAG, "Failed to create client_queue");
607 return;
608 }
609
610 ws_server_start();
611 xTaskCreate(&task_HTTPserver, "HTTPserver", 5000, NULL, 9, &xTaskHTTP);
612 xTaskCreate(&task_Queue, "Queue", 6000, NULL, 6, &xTaskQueue);
613 }
614

mercurial