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 |
|