main/task_http.c

branch
idf 5.1
changeset 142
1f7069278fe7
parent 137
e0f50087c909
--- a/main/task_http.c	Mon Jul 01 08:38:57 2024 +0200
+++ b/main/task_http.c	Wed Jul 03 20:01:31 2024 +0200
@@ -1,23 +1,21 @@
 /**
  * @file task_http.c
  * @brief HTTP and Websocket server functions.
- *        This uses some ESP32 Websocket code written by Blake Felt - blake.w.felt@gmail.com
  */
 #include "config.h"
+#include "sys/param.h"
 #include "mbedtls/base64.h"
 #include "mbedtls/sha1.h"
 #include "cJSON.h"
 
 
 static const char		*TAG = "task_http";
-static QueueHandle_t		client_queue;
-const static int		client_queue_size = 10;
-static TaskHandle_t             xTaskHTTP  = NULL;
-static TaskHandle_t             xTaskQueue = NULL;
+httpd_handle_t			web_server = NULL;			///< The http server handle.
 
 cJSON				*root = NULL;
 cJSON				*touch = NULL;
 
+
 typedef struct _ls_list {
     struct _ls_list		*next;
     char			d_name[64];
@@ -26,7 +24,9 @@
 } ls_list;
 
 
-
+/**
+ * @brief Clear the linked list and release memory.
+ */
 void tidy_lslist(ls_list **lap)
 {
     ls_list	*tmp, *old;
@@ -39,7 +39,13 @@
 }
 
 
-
+/**
+ * @brief Add a file entry to the linked list.
+ * @param lap Pointer to the linked list.
+ * @param name The entry filename.
+ * @param size The entry filesize.
+ * @param mtime The entry filedate.
+ */
 void fill_list(ls_list **lap, char *name, off_t size, long mtime)
 {
     ls_list	**tmp;
@@ -55,7 +61,11 @@
 }
 
 
-
+/**
+ * @brief Compare for sorting files by datetime, reversed.
+ * @param lsp1 Entry 1
+ * @param lsp2 Entry 2
+ */
 int comp(ls_list **lsp1, ls_list **lsp2)
 {
     char	as[20], bs[20];
@@ -67,7 +77,9 @@
 }
 
 
-
+/**
+ * @brief Sort the linked list.
+ */
 void sort_list(ls_list **lap)
 {
     ls_list	*ta, **vector;
@@ -101,74 +113,199 @@
 }
 
 
+#define CHECK_FILE_EXTENSION(filename, ext) (strcasecmp(&filename[strlen(filename) - strlen(ext)], ext) == 0)
 
 /**
- * @brief Debug dump buffer
- * @param buf The buffer
- * @param buflen Length of the buffer
+ * @brief Set HTTP response content type according to file extension
+ * @param req The request where the content type will be set.
+ * @param filepath The filename to get the extension from.
+ * @return ESP_OK if success.
  */
-#if 0
-void dump_buf(char *buf, uint16_t buflen);
-#endif
+static esp_err_t set_content_type_from_file(httpd_req_t *req, const char *filepath)
+{
+    const char *type = "text/plain";
+    if (CHECK_FILE_EXTENSION(filepath, ".html")) {
+        type = "text/html";
+    } else if (CHECK_FILE_EXTENSION(filepath, ".log")) {
+	type = "text/plain";
+    } else if (CHECK_FILE_EXTENSION(filepath, ".js")) {
+        type = "application/javascript";
+    } else if (CHECK_FILE_EXTENSION(filepath, ".json")) {
+	type = "text/json";
+    } else if (CHECK_FILE_EXTENSION(filepath, ".gz")) {
+	type = "application/x-gzip";
+    } else if (CHECK_FILE_EXTENSION(filepath, ".css")) {
+        type = "text/css";
+    } else if (CHECK_FILE_EXTENSION(filepath, ".png")) {
+        type = "image/png";
+    } else if (CHECK_FILE_EXTENSION(filepath, ".ico")) {
+        type = "image/x-icon";
+    } else if (CHECK_FILE_EXTENSION(filepath, ".svg")) {
+        type = "text/xml";
+    } else if (CHECK_FILE_EXTENSION(filepath, ".ttf")) {
+	type = "font/ttf";
+    } else if (CHECK_FILE_EXTENSION(filepath, ".woff")) {
+	type = "font/woff";
+    } else if (CHECK_FILE_EXTENSION(filepath, ".woff2")) {
+        type = "font/woff2";
+    } else if (CHECK_FILE_EXTENSION(filepath, ".xml")) {
+	type = "text/xml";
+    }
+    return httpd_resp_set_type(req, type);
+}
 
 
 /**
- * @brief Send HTTP error and message
- * @param conn The socket to send to.
- * @param error The HTTP error code.
- * @param message Yhe human readable explanation.
- * @paeam body The human readable explanation.
+ * @brief Send text message to a single websocket client.
+ * @param num The client socket to send to.
+ * @param msg The message to send.
+ * @return ESP_OK.
  */
-static void http_error(struct netconn *conn, int error, char *message, char *body)
+int ws_server_send_text_client(int num, char* msg)
 {
-    char	*tmp = malloc(200);
+    httpd_ws_frame_t    ws_pkt;
+
+    memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
+    ws_pkt.type = HTTPD_WS_TYPE_TEXT;
+    ws_pkt.len = strlen(msg);
+    ws_pkt.payload = (uint8_t*)msg;
+    httpd_ws_send_frame_async(web_server, num, &ws_pkt);
+    ESP_LOGD(TAG, "ws_server_send_text_client(%d, %s)", num, msg);
+    return ESP_OK;
+}
+
 
-    ESP_LOGI(TAG, "Http response %d - %s", error, message);
-    if (strlen(body)) {
-	snprintf(tmp, 199, "HTTP/1.1 %d %s\r\nContent-type: text/plain\r\n\r\n%s\n", error, message, body);
-    } else {
-        snprintf(tmp, 199, "HTTP/1.1 %d %s\r\n\r\n", error, message);
+/**
+ * @brief Broadcast text message to all connected websocket clients.
+ * @param msg The text message.
+ * @return -1 if error, else the number of clients.
+ */
+int ws_server_send_text_clients(char* msg)
+{
+    httpd_ws_frame_t	ws_pkt;
+    static size_t	max_clients = CONFIG_LWIP_MAX_LISTENING_TCP;
+    size_t		fds = max_clients;
+    int			client_fds[CONFIG_LWIP_MAX_LISTENING_TCP] = {0};
+    int			count = 0;
+
+    memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
+    ws_pkt.type = HTTPD_WS_TYPE_TEXT;
+    ws_pkt.len = strlen(msg);
+    ws_pkt.payload = (uint8_t*)msg;
+
+    esp_err_t ret = httpd_get_client_list(web_server, &fds, client_fds);
+    if (ret != ESP_OK) {
+        return -1;
     }
-    netconn_write(conn, tmp, strlen(tmp), NETCONN_NOCOPY);
-    free(tmp);
+
+    for (int i = 0; i < fds; i++) {
+        httpd_ws_client_info_t client_info = httpd_ws_get_fd_info(web_server, client_fds[i]);
+        if (client_info == HTTPD_WS_CLIENT_WEBSOCKET) {
+            httpd_ws_send_frame_async(web_server, client_fds[i], &ws_pkt);
+            count++;
+        }
+    }
+    return count;
 }
 
 
+/**
+ * @brief Websocket handler.
+ * @param req The request structure.
+ * @return ESP_OK or error.
+ */
+static esp_err_t ws_handler(httpd_req_t *req)
+{
+    if (req->method == HTTP_GET) {
+        ESP_LOGI(TAG, "New websocket connection %d", httpd_req_to_sockfd(req));
+	/* Send initial screen */
+	TFTstartWS(httpd_req_to_sockfd(req));
+        return ESP_OK;
+    }
+
+    httpd_ws_frame_t	ws_pkt;
+    uint8_t		*buf = NULL;
+    char		jbuf[128];
+    cJSON		*root = NULL, *touch = NULL;
+
+    memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
+    ws_pkt.type = HTTPD_WS_TYPE_TEXT;
+    /* Set max_len = 0 to get the frame len */
+    esp_err_t ret = httpd_ws_recv_frame(req, &ws_pkt, 0);
+    if (ret != ESP_OK) {
+        ESP_LOGE(TAG, "ws_handler() httpd_ws_recv_frame failed to get frame len with %d", ret);
+        return ret;
+    }
+    ESP_LOGD(TAG, "frame len is %d type is %d socket %d", ws_pkt.len, ws_pkt.type, httpd_req_to_sockfd(req));
+
+    if (ws_pkt.len) {
+        /* ws_pkt.len + 1 is for NULL termination as we are expecting a string */
+        buf = calloc(1, ws_pkt.len + 1);
+        if (buf == NULL) {
+            ESP_LOGE(TAG, "ws_handler() no memory for buf");
+            return ESP_ERR_NO_MEM;
+        }
+        ws_pkt.payload = buf;
+        /* Set max_len = ws_pkt.len to get the frame payload */
+        ret = httpd_ws_recv_frame(req, &ws_pkt, ws_pkt.len);
+        if (ret != ESP_OK) {
+            ESP_LOGE(TAG, "ws_handler() httpd_ws_recv_frame failed with %d", ret);
+            free(buf);
+            return ret;
+        }
+        ESP_LOGI(TAG, "ws_handler() Got message: %s", ws_pkt.payload);
+    }
+
+    if ((ws_pkt.type == HTTPD_WS_TYPE_TEXT) && (ws_pkt.len > 0) && (ws_pkt.len < 128)) {
+        memcpy(jbuf, ws_pkt.payload, ws_pkt.len);
+        jbuf[ws_pkt.len] = '\0';
+        if ((root = cJSON_Parse(jbuf))) {
+            if ((touch = cJSON_GetObjectItem(root,"touch"))) {
+                int x = cJSON_GetObjectItem(touch, "x")->valueint;
+                int y = cJSON_GetObjectItem(touch, "y")->valueint;
+		WS_touched(x, y);
+            } else {
+                ESP_LOGI(TAG,"not json touch");
+            }
+            cJSON_Delete(root);
+        } else {
+            ESP_LOGI(TAG,"not json");
+        }
+    }
+
+    free(buf);
+    return ESP_OK;
+}
+
 
 /**
- * @brief Send HTTP file from the filesystem.
- * @param conn The network connection.
- * @param url The requested url.
- * @param mod_since The date/time of the file, or NULL.
- * @param ipstr The IP address of the remote.
+ * @brief Handle files download from /spiffs or /sdcard.
  */
-static void http_sendfile(struct netconn *conn, char *url, char *mod_since, char *ipstr)
+static esp_err_t files_handler(httpd_req_t *req)
 {
-    char	temp_url[128], temp_url_gz[132], header[256], c_type[32];
+    char	temp_url[560], temp_url_gz[564];
     struct stat	st;
     off_t	filesize;
-    size_t	sentsize;
     char	strftime_buf[64];
-    err_t	err;
     bool	send_gz;
-    FILE	*f;
+    int		fd = -1;
 
-    if (url[strlen(url) - 1] == '/') {
-	// If no filename given, use index.html
-	sprintf(temp_url, "/spiffs/w%sindex.html", url);
-    } else if (strncmp(url, "/log/", 5) == 0) {
-	// Logfiles are on the SD card.
-	sprintf(temp_url, "/sdcard/w%s", url);
+    if (req->uri[strlen(req->uri) - 1] == '/') {
+        // If no filename given, use index.html
+        sprintf(temp_url, "/spiffs/w%sindex.html", req->uri);
+    } else if (strncmp(req->uri, "/log/", 5) == 0) {
+        // Logfiles are on the SD card.
+        sprintf(temp_url, "/sdcard/w%s", req->uri);
     } else {
-	sprintf(temp_url, "/spiffs/w%s", url);
-	for (int i = 0; i < 127; i++) {
-	    if (temp_url[i] == '?')
-		temp_url[i] = '\0';
-	    if (temp_url[i] == '\0')
-		break;
-	}
+        sprintf(temp_url, "/spiffs/w%s", req->uri);
+        for (int i = 0; i < 127; i++) {
+            if (temp_url[i] == '?')
+                temp_url[i] = '\0';
+            if (temp_url[i] == '\0')
+                break;
+        }
     }
-    snprintf(temp_url_gz, 131, "%s.gz", temp_url);
+    snprintf(temp_url_gz, 563, "%s.gz", temp_url);
 
     /*
      * Get filesize and date for the response headers.
@@ -180,6 +317,7 @@
     strftime_buf[0] = '\0';
     send_gz = false;
     if (stat(temp_url_gz, &st) == 0) {
+	/* The requested file is gzipped. */
 	filesize = st.st_size;
 	strftime(strftime_buf, sizeof(strftime_buf), "%a, %d %b %Y %T %z", localtime(&(st.st_mtime)));
 	send_gz = true;
@@ -187,428 +325,201 @@
 	filesize = st.st_size;
 	strftime(strftime_buf, sizeof(strftime_buf), "%a, %d %b %Y %T %z", localtime(&(st.st_mtime)));
     }
-
-    /*
-     * If we have a If-Modified-Since parameter, compare that with the current
-     * filedate on disk. If It's the same send a 304 response.
-     * Cannot work on /spiffs.
-     * Update 18-05-2021 it seems better on idf 4.2.1 but still not correct.
-     */
-#if 0
-    time_t    Now;
-    struct tm timeInfo;
-    if (mod_since && strlen(strftime_buf)) {
-	time(&Now);
-	localtime_r(&Now, &timeInfo);
-	strftime(strftime_buf, sizeof(strftime_buf), "%a, %d %b %Y %T %z", &timeInfo);
-	sprintf(header, "HTTP/1.1 304 Not Modified\r\nDate: %s\r\n\r\n", strftime_buf);
-	netconn_write(conn, header, strlen(header), NETCONN_NOCOPY);
-	ESP_LOGI(TAG, "%s sendfile %s Not Modified, ok", ipstr, temp_url);
-	return;
-    }
-#endif
+    ESP_LOGI(TAG, "GET `%s` file %s date %s size %d", req->uri, (send_gz) ? temp_url_gz : temp_url, strftime_buf, (int)filesize);
 
     if (send_gz) {
-	f = fopen(temp_url_gz, "r");
+	fd = open(temp_url_gz, O_RDONLY, 0);
     } else {
-    	f = fopen(temp_url, "r");
+	fd = open(temp_url, O_RDONLY, 0);
     }
-    if (f == NULL) {
-	ESP_LOGI(TAG, "%s url \'%s\' file \'%s\' not found", ipstr, url, temp_url);
-	http_error(conn, 404, (char *)"Not found", (char *)"Not found");
-	return;
+    if (fd == -1) {
+	ESP_LOGI(TAG, "files_handler() file not found");
+	httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "File does not exist");
+	return ESP_FAIL;
+    }
+
+    set_content_type_from_file(req, temp_url);
+    if (send_gz) {
+	httpd_resp_set_hdr(req, "Content-Encoding", "gzip");
     }
 
-    if (strcmp(".html", &temp_url[strlen(temp_url) - 5]) == 0) {
-	sprintf(c_type, "text/html");
-    } else if (strcmp(".css", &temp_url[strlen(temp_url) - 4]) == 0) {
-	sprintf(c_type, "text/css");
-    } else if (strcmp(".log", &temp_url[strlen(temp_url) - 4]) == 0) {
-        sprintf(c_type, "text/plain");
-    } else if (strcmp(".js", &temp_url[strlen(temp_url) - 3]) == 0) {
-	sprintf(c_type, "text/javascript");
-    } else if (strcmp(".json", &temp_url[strlen(temp_url) - 5]) == 0) {
-	sprintf(c_type, "text/json");
-    } else if (strcmp(".gz", &temp_url[strlen(temp_url) - 3]) == 0) {
-	sprintf(c_type, "application/x-gzip");
-    } else if (strcmp(".png", &temp_url[strlen(temp_url) - 4]) == 0) {
-	sprintf(c_type, "image/png");
-    } else if (strcmp(".svg", &temp_url[strlen(temp_url) - 4]) == 0) {
-	sprintf(c_type, "image/svg+xml");
-    } else if (strcmp(".oga", &temp_url[strlen(temp_url) - 4]) == 0) {
-	sprintf(c_type, "audio/ogg");
-    } else if (strcmp(".ico", &temp_url[strlen(temp_url) - 4]) == 0) {
-	sprintf(c_type, "image/x-icon");
-    } else if (strcmp(".ttf", &temp_url[strlen(temp_url) - 4]) == 0) {
-	sprintf(c_type, "font/ttf");
-    } else if (strcmp(".woff", &temp_url[strlen(temp_url) - 5]) == 0) {
-	sprintf(c_type, "font/woff");
-    } else if (strcmp(".woff2", &temp_url[strlen(temp_url) - 6]) == 0) {
-	sprintf(c_type, "font/woff2");
-    } else if (strcmp(".xml", &temp_url[strlen(temp_url) - 4]) == 0) {
-	sprintf(c_type, "text/xml");
-    } else {
-	sprintf(c_type, "application/octet-stream");
-	printf("Unknown content type for %s\n", temp_url);
-    }
+    /*
+     * Now the real file send in chunks.
+     */
+    char *chunk = malloc(1024);
+    ssize_t read_bytes;
+    do {
+        read_bytes = read(fd, chunk, 1024);
+        if (read_bytes == -1) {
+            ESP_LOGE(TAG, "files_handler() Failed to read file : %s", (send_gz) ? temp_url_gz : temp_url);
+        } else if (read_bytes > 0) {
+            /* Send the buffer contents as HTTP response chunk */
+            if (httpd_resp_send_chunk(req, chunk, read_bytes) != ESP_OK) {
+                close(fd);
+                ESP_LOGE(TAG, "files_handler() File sending failed!");
+                /* Abort sending file */
+                httpd_resp_sendstr_chunk(req, NULL);
+                /* Respond with 500 Internal Server Error */
+                httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to send file");
+                return ESP_FAIL;
+            }
+        }
+    } while (read_bytes > 0);
+    /* Close file after sending complete */
+    close(fd);
 
-    vTaskDelay(2 / portTICK_PERIOD_MS);
-    //  httpdHeader(connData, "Cache-Control", "max-age=3600, must-revalidate");
-    if (filesize) {
-	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);
-    } else {
-	sprintf(header, "HTTP/1.1 200 OK\r\nContent-type: %s\r\n", c_type);
-    }
-    if (send_gz) {
-	strncat(header, "Content-Encoding: gzip\r\n", 255 - strlen(header));
-    }
-    strncat(header, "\r\n", 255 - strlen(header));	// Add last empty line.
-    err = netconn_write(conn, header, strlen(header), NETCONN_NOCOPY);
-    if (err != ERR_OK) {
-	ESP_LOGW(TAG, "%s sendfile %s%s err=%d on header write", ipstr, temp_url, (send_gz) ? ".gz":"", err);
-	fclose(f);
-	return;
-    }
-    // if (strstr(acceptEncodingBuffer, "gzip") == NULL)
-    // http_error(conn, 501, "Not implemented", "Your browser does not accept gzip-compressed data.");
-
-    sentsize = 0;
-    uint8_t	*buff = malloc(1024);
-    size_t      bytes;
-    int		pause = 0;
-
-    for (;;) {
-	bytes = fread(buff, 1, 1024, f);
-	if (bytes == 0)
-		break;
-
-	err = netconn_write(conn, buff, bytes, NETCONN_NOCOPY);
-	if (err != ERR_OK) {
-	    ESP_LOGW(TAG, "%s sendfile %s%s err=%d send %u bytes of %ld bytes", ipstr, temp_url,  (send_gz) ? ".gz":"", err, sentsize, filesize);
-	    break;
-	}
-	vTaskDelay(2 / portTICK_PERIOD_MS);
-	sentsize += bytes;
-	pause++;
-	if (pause > 50) { // 50 K
-	    pause = 0;
-	    vTaskDelay(50 / portTICK_PERIOD_MS);
-	}
-    }
-    fclose(f);
-    free(buff);
-	
-    if (sentsize == filesize) {
-	ESP_LOGI(TAG, "%s sendfile %s%s sent %u bytes, ok (%s)", ipstr, temp_url,  (send_gz) ? ".gz":"", sentsize, url);
-    }
+    /* Respond with an empty chunk to signal HTTP response completion */
+    httpd_resp_send_chunk(req, NULL, 0);
+    return ESP_OK;
 }
 
 
-
 /**
- * @brief Handle web ui websocket events.
+ * @brief Handle 'GET /logfiles.json'
  */
-void websock_callback(uint8_t num, WEBSOCKET_TYPE_t type, char* msg, uint64_t len)
+static esp_err_t logfiles_handler(httpd_req_t *req)
 {
-    char	jbuf[128];
+    ls_list     *lsx = NULL, *tmp;
+    char        temp[64];
+    FILE        *dest = fopen("/spiffs/w/logfiles.json", "w");
+
+    if (dest) {
+        DIR *dir = opendir("/sdcard/w/log");
+        if (dir) {
+            struct dirent* de = readdir(dir);
+            struct stat st;
+            while (de) {
+                snprintf(temp, 63, "/sdcard/w/log/");
+                strncat(temp, de->d_name, 63 - strlen(temp));
+                if (stat(temp, &st) == ESP_OK) {
+                    fill_list(&lsx, de->d_name, st.st_size, st.st_mtime);
+                }
+                de = readdir(dir);
+                vTaskDelay(5 / portTICK_PERIOD_MS);
+            }
+            closedir(dir);
+        } else {
+            ESP_LOGE(TAG, "Error %d open directory /sdcard/w/log", errno);
+        }
 
-    switch(type) {
-	case WEBSOCKET_CONNECT:
-	case WEBSOCKET_DISCONNECT_EXTERNAL:
-			break;
+        sort_list(&lsx);
+        bool comma = false;
+        fprintf(dest, "{\"Dir\":[{\"Folder\":\"/log\",\"Files\":[");
+        for (tmp = lsx; tmp; tmp = tmp->next) {
+            fprintf(dest, "%s{\"File\":\"%s\",\"Size\":%ld,\"Date\":%ld}", (comma)?",":"", tmp->d_name, tmp->size, tmp->mtime);
+            comma = true;
+        }
+        fprintf(dest, "]}]}");
+        fclose(dest);
+        tidy_lslist(&lsx);
+    } else {
+        ESP_LOGE(TAG, "Error %d write /spiffs/w/logfiles.json", errno);
+	httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to create file");
+        return ESP_FAIL;
+    }
 
-	case WEBSOCKET_DISCONNECT_INTERNAL:
-			ESP_LOGI(TAG,"Websocket client %i was disconnected",num);
-			break;
+    /*
+     * File is ready, send it using the files_handler().
+     */
+    esp_err_t ret = files_handler(req);
+    unlink("/spiffs/w/logfiles.json");
+    return ret;
+}
 
-	case WEBSOCKET_DISCONNECT_ERROR:
-			ESP_LOGI(TAG,"Websocket client %i was disconnected due to an error",num);
-			break;
+
+esp_err_t start_webserver(void)
+{
+    httpd_config_t config = HTTPD_DEFAULT_CONFIG();
+    config.lru_purge_enable = true;
+    config.uri_match_fn = httpd_uri_match_wildcard;
 
-	case WEBSOCKET_TEXT:
-			/*
-			 * Handle json actions from the web clients, like button presses.
-			 */
-			if (len < 128) { // Safety, messages are small.
-			    memcpy(jbuf, msg, len);
-                            jbuf[len] = '\0';
-			    if ((root = cJSON_Parse(jbuf))) {
-				if ((touch = cJSON_GetObjectItem(root,"touch"))) {
-				    int x = cJSON_GetObjectItem(touch, "x")->valueint;
-				    int y = cJSON_GetObjectItem(touch, "y")->valueint;
-				    WS_touched(x, y);
-				    break;
-				} else {
-				    ESP_LOGI(TAG,"not json touch");
-				}
-				cJSON_Delete(root);
-			    } else {
-				ESP_LOGI(TAG,"not json");
-			    }
-			}
-			// Log if the message in not processed.
-			ESP_LOGI(TAG,"Websocket client %i sent text message of size %i:\n%s",num,(unsigned)len,msg);
-			break;
+    // Start the httpd server
+    if (web_server == NULL) {
+	ESP_LOGI(TAG, "Starting httpd on port: '%d'", config.server_port);
+    	if (httpd_start(&web_server, &config) == ESP_OK) {
+            // Set URI handlers
+            ESP_LOGD(TAG, "Registering URI handlers");
+
+	    httpd_uri_t ws = {
+		.uri        = "/ws",
+		.method     = HTTP_GET,
+		.handler    = ws_handler,
+		.user_ctx   = NULL,
+		.is_websocket = true
+	    };
+	    httpd_register_uri_handler(web_server, &ws);
+
+	    httpd_uri_t logfiles = {
+		.uri = "/logfiles.json",
+		.method = HTTP_GET,
+		.handler = logfiles_handler,
+		.user_ctx   = NULL
+	    };
+	    httpd_register_uri_handler(web_server, &logfiles);
 
-	case WEBSOCKET_BIN:
-			ESP_LOGI(TAG,"Websocket client %i sent bin message of size %i:\n",num,(unsigned)len);
-			break;
+	    httpd_uri_t vfs_files = {
+            	.uri = "/*",
+            	.method = HTTP_GET,
+            	.handler = files_handler,
+            	.user_ctx = NULL
+	    };
+	    httpd_register_uri_handler(web_server, &vfs_files);
+            return ESP_OK;
+	}
+    } else {
+	ESP_LOGI(TAG, "httpd server already started");
+	return ESP_OK;
+    }
+
+    ESP_LOGI(TAG, "Error starting server!");
+    return ESP_FAIL;
+}
+
 
-	case WEBSOCKET_PING:
-			ESP_LOGI(TAG,"client %i pinged us with message of size %i:\n%s",num,(unsigned)len,msg);
-			break;
+static esp_err_t stop_webserver(void)
+{
+    esp_err_t	ret;
+
+    ESP_LOGI(TAG, "Stopping httpd server");
+    ret = httpd_stop(web_server);
+    web_server = NULL;
+    return ret;
+}
 
-	case WEBSOCKET_PONG:
-			ESP_LOGI(TAG,"client %i responded to the ping",num);
-			break;
+
+static void disconnect_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data)
+{
+    if (web_server) {
+        if (stop_webserver() == ESP_OK) {
+            web_server = NULL;
+        } else {
+            ESP_LOGE(TAG, "Failed to stop http server");
+        }
     }
 }
 
 
-
-#if 0
-void dump_buf(char *buf, uint16_t buflen)
+static void connect_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data)
 {
-    int i = 0, l = 1;
-
-    printf("request length %d\n00: ", buflen);
-    for (;;) {
-	if (i >= buflen)
-	    break;
-	if ((buf[i] < ' ') || (buf[i] > 126)) {
-	    if (buf[i] == '\n') {
-		printf("\\n\n%02d: ", l);
-		l++;
-	    } else if (buf[i] == '\r') {
-		printf("\\r");
-	    } else {
-		printf("\\%02x", buf[i]);
-	    }
-	} else {
-	    printf("%c", buf[i]);
-	}
-	i++;
-    }
-    printf("\n");
+    start_webserver();
 }
-#endif
-
 
 
 /**
- * @brief Serve any client.
- * @param http_client_sock The socket on which the client is connected.
+ * @brief Install the HTTP server event handlers.
+ *        The real server is started and stopped on events.
  */
-static void http_serve(struct netconn *conn)
+void install_http_server(void)
 {
-    char		ipstr[IPADDR_STRLEN_MAX];
-    struct netbuf	*inbuf;
-    static char		*buf;
-    static uint16_t	buflen;
-    static err_t	err;
-    char		url[128], *p, *mod_since = NULL;
-    ip_addr_t		remote_addr;
-    uint16_t		remote_port;
-    ls_list		*lsx = NULL, *tmp;
-
-    if (netconn_getaddr(conn, &remote_addr, &remote_port, 0) == ERR_OK) {
-	strcpy(ipstr, ip4addr_ntoa(ip_2_ip4(&remote_addr)));
-    } else {
-	ipstr[0] = '\0';
-    }
-
-    netconn_set_recvtimeout(conn,1000); // allow a connection timeout of 1 second
-    err = netconn_recv(conn, &inbuf);
-
-    if (err != ERR_OK) {
-	if (err != ERR_TIMEOUT) {	// Ignore timeout
-	    ESP_LOGW(TAG,"%s error %d on read", ipstr, err);
-	}
-	netconn_close(conn);
-	netconn_delete(conn);
-	netbuf_delete(inbuf);
-	return;
-    }
-
-    netbuf_data(inbuf, (void**)&buf, &buflen);
-
-    if (buf) {
-	/*
-	 * Build url string from the data buffer.
-	 * It looks like: GET /app/localization.js HTTP/1.1
-	 */
-	for (int i = 0; i < 10; i++) {
-	    if (buf[i] == ' ') {
-		i++;
-		for (int j = i; j < 128; j++) {
-		    url[j-i] = buf[j];
-		    if (url[j-i] == ' ') {
-			url[j-i] = '\0';
-			break;
-		    }
-		}
-		break;
-	    }
-	}
+    ESP_LOGI(TAG, "Install HTTP server");
 
-	if (strstr(buf, "GET /logfiles.json")) {
-	    char temp[64];
-	    FILE *dest = fopen("/spiffs/w/logfiles.json", "w");
-	    if (dest) {
-	    	DIR *dir = opendir("/sdcard/w/log");
-	    	if (dir) {
-		    struct dirent* de = readdir(dir);
-		    struct stat st;
-		    while (de) {
-		    	snprintf(temp, 63, "/sdcard/w/log/");
-			strncat(temp, de->d_name, 63 - strlen(temp));
-		    	if (stat(temp, &st) == ESP_OK) {
-			    fill_list(&lsx, de->d_name, st.st_size, st.st_mtime);
-		    	}
-		    	de = readdir(dir);
-		    	vTaskDelay(5 / portTICK_PERIOD_MS);
-		    }
-		    closedir(dir);
-	    	} else {
-		    ESP_LOGE(TAG, "Error %d open directory /sdcard/w/log", errno);
-		}
-
-		sort_list(&lsx);
-		bool comma = false;
-		fprintf(dest, "{\"Dir\":[{\"Folder\":\"/log\",\"Files\":[");
-		for (tmp = lsx; tmp; tmp = tmp->next) {
-		    fprintf(dest, "%s{\"File\":\"%s\",\"Size\":%ld,\"Date\":%ld}", (comma)?",":"", tmp->d_name, tmp->size, tmp->mtime);
-		    comma = true;
-		}
-	    	fprintf(dest, "]}]}");
-	    	fclose(dest);
-		tidy_lslist(&lsx);
-	    } else {
-		ESP_LOGE(TAG, "Error %d write /spiffs/w/logfiles.json", errno);
-	    }
-	    http_sendfile(conn, (char *)"/logfiles.json", NULL, ipstr);
-	    netconn_close(conn);
-	    netconn_delete(conn);
-	    netbuf_delete(inbuf);
-	    unlink("/spiffs/w/logfiles.json");
-	    return;
-	}
-
-	// http requests
-	if (! strstr(buf,"Upgrade: websocket")) {	// Not websocket
-	    p = strstr(buf, "If-Modified-Since:");	// May be cached
-	    if (p) {
-		size_t mod_len = strcspn(p, " ");
-		p += (int)(mod_len + 1);
-		mod_len = strcspn(p, "\r\n");
-		mod_since = malloc(mod_len + 2);
-		memcpy(mod_since, p, mod_len);
-		mod_since[mod_len] = '\0';
-	    }
-	    http_sendfile(conn, url, mod_since, ipstr);
-	    if (mod_since)
-	        free(mod_since);
-	    mod_since = NULL;
-	    netconn_close(conn);
-	    netconn_delete(conn);
-	    netbuf_delete(inbuf);
-	    return;
-	}
-
-	// websocket for web UI.
-	if ((strstr(buf,"GET /ws ") && strstr(buf,"Upgrade: websocket"))) {
-	    int nr = ws_server_add_client_protocol(conn, buf, buflen, (char *)"/ws", (char *)"binary", websock_callback);
-	    ESP_LOGI(TAG, "%s new websocket on /ws client: %d", ipstr, nr);
-	    netbuf_delete(inbuf);
-	    TFTstartWS(nr);
-	    // Startup something? Init webscreen?
-	    return;
-	}
-
-#if 0
-	dump_buf(buf, buflen);
-#endif
-
-	if (strstr(buf, "GET /")) {
-		ESP_LOGI(TAG, "%s request: %s", ipstr, buf);
-		http_error(conn, 404, (char *)"Not found", (char *)"Not found");
-	} else {
-		http_error(conn, 405, (char *)"Invalid method", (char *)"Invalid method");
-	}
-    }
-
-    netconn_close(conn);
-    netconn_delete(conn);
-    netbuf_delete(inbuf);
+    /*
+     * Respond to WiFi and network events. This is much better then letting the
+     * server run forever.
+     */
+    ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &connect_handler, web_server));
+    ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_LOST_IP, &disconnect_handler, web_server));
+    ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, &disconnect_handler, web_server));
 }
 
 
-
-/**
- * @brief Handles clients when they first connect. passes to a queue
- */
-static void task_HTTPserver(void* pvParameters)
-{
-    struct netconn	*conn, *newconn;
-    static err_t	err;
-
-    ESP_LOGI(TAG, "Start HTTP/web server");
-
-    conn = netconn_new(NETCONN_TCP);
-    netconn_bind(conn,NULL,80);
-    netconn_listen(conn);
-
-    do {
-	err = netconn_accept(conn, &newconn);
-	if (err == ERR_OK) {
-	    if (xQueueSendToBack(client_queue,&newconn,portMAX_DELAY) != pdTRUE) {
-		ESP_LOGW(TAG, "xQueueSendToBack() queue full");	    
-	    };
-	}
-	vTaskDelay(5 / portTICK_PERIOD_MS);
-    } while (err == ERR_OK);
-
-    ESP_LOGE(TAG, "Stopping HTTP/web server");
-    netconn_close(conn);
-    netconn_delete(conn);
-    vTaskDelete(NULL);
-}
-
-
-
-/**
- * @brief Receives clients from queue and handle them.
- */
-static void task_Queue(void* pvParameters)
-{
-    struct netconn* conn;
-
-    ESP_LOGI(TAG, "Start Queue task");
-    for(;;) {
-	xQueueReceive(client_queue, &(conn), portMAX_DELAY);
-	if (!conn)
-	    continue;
-	http_serve(conn);
-	vTaskDelay(2 / portTICK_PERIOD_MS);
-    }
-    ESP_LOGE(TAG, "Stopping Queue task");
-    vTaskDelete(NULL);
-}
-
-
-
-void start_http_websocket(void)
-{
-    ESP_LOGI(TAG, "Start HTTP/Websocket server");
-
-    client_queue = xQueueCreate(client_queue_size,sizeof(struct netconn*));
-    if (client_queue == 0) {
-        ESP_LOGE(TAG, "Failed to create client_queue");
-        return;
-    }
-
-    ws_server_start();
-    xTaskCreate(&task_HTTPserver, "HTTPserver", 5000, NULL, 9, &xTaskHTTP);
-    xTaskCreate(&task_Queue,      "Queue",      6000, NULL, 6, &xTaskQueue);
-}
-

mercurial