main/task_http.c

changeset 0
b74b0e4902c3
child 1
ad2c8b13eb88
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main/task_http.c	Sat Oct 20 13:23:15 2018 +0200
@@ -0,0 +1,529 @@
+/**
+ * @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 "mbedtls/base64.h"
+#include "mbedtls/sha1.h"
+
+
+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;
+
+
+
+void dump_buf(char *buf, uint16_t buflen);
+
+
+/**
+ * @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.
+ */
+static void http_error(struct netconn *conn, int error, char *message, char *body)
+{
+    char	*tmp = malloc(200);
+
+    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);
+    }
+    netconn_write(conn, tmp, strlen(tmp), NETCONN_NOCOPY);
+    free(tmp);
+}
+
+
+
+/**
+ * @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.
+ */
+static void http_sendfile(struct netconn *conn, char *url, char *mod_since, char *ipstr)
+{
+    char	temp_url[128], temp_url_gz[128], header[128], c_type[32];
+    struct stat	st;
+    off_t	filesize;
+    size_t	sentsize;
+    char	strftime_buf[64];
+    err_t	err;
+    bool	send_gz;
+    FILE	*f;
+
+    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);
+    } 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_gz, "%s.gz", temp_url);
+
+    /*
+     * Get filesize and date for the response headers.
+     * Note that the /spiffs filesystem doesn't support file timestamps
+     * and returns the epoch for each file. Therefore we cannot use
+     * the cache based on timestamps.
+     */
+    filesize = 0;
+    strftime_buf[0] = '\0';
+    send_gz = false;
+    if (stat(temp_url_gz, &st) == 0) {
+	filesize = st.st_size;
+	strftime(strftime_buf, sizeof(strftime_buf), "%a, %d %b %Y %T %z", localtime(&(st.st_mtime)));
+	send_gz = true;
+    } else if (stat(temp_url, &st) == 0) {
+	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.
+     */
+#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, "http_sendfile %s Not Modified, ok", temp_url);
+	return;
+    }
+#endif
+
+    if (send_gz) {
+	f = fopen(temp_url_gz, "r");
+    } else {
+    	f = fopen(temp_url, "r");
+    }
+    if (f == NULL) {
+	ESP_LOGI(TAG, "%s url \'%s\' file \'%s\' not found", ipstr, url, temp_url);
+	http_error(conn, 404, "Not found", "Not found");
+	return;
+    }
+
+    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(".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(".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);
+    }
+
+    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) {
+	sprintf(header, "%sContent-Encoding: gzip\r\n", header);
+    }
+    sprintf(header, "%s\r\n", header);	// Add last empty line.
+    err = netconn_write(conn, header, strlen(header), NETCONN_NOCOPY);
+    if (err != ERR_OK) {
+	ESP_LOGE(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_LOGE(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);
+    }
+}
+
+
+
+/**
+ * @brief Handle VNC websocket events.
+ */
+void websockify_callback(uint8_t num, WEBSOCKET_TYPE_t type, char* msg, uint64_t len)
+{
+    switch(type) {
+	case WEBSOCKET_CONNECT:
+			ESP_LOGI(TAG,"Websockify client %i connected!",num);
+			break;
+
+	case WEBSOCKET_DISCONNECT_EXTERNAL:
+			ESP_LOGI(TAG,"Websockify client %i sent a disconnect message",num);
+			VncStopWS(num);
+			break;
+
+	case WEBSOCKET_DISCONNECT_INTERNAL:
+			ESP_LOGI(TAG,"Websockify client %i was disconnected",num);
+			VncStopWS(num);
+			break;
+
+	case WEBSOCKET_DISCONNECT_ERROR:
+			ESP_LOGI(TAG,"Websockify client %i was disconnected due to an error",num);
+			VncStopWS(num);
+			break;
+	
+	case WEBSOCKET_TEXT:
+			ESP_LOGI(TAG,"Websockiify client %i sent text message of size %i:\n%s",num,(uint32_t)len,msg);
+			break;
+
+	case WEBSOCKET_BIN:
+			VncGetWSmessage(msg, len);
+			//dump_buf(msg, len);	
+			break;
+
+	case WEBSOCKET_PING:
+			ESP_LOGI(TAG,"client %i pinged us with message of size %i:\n%s",num,(uint32_t)len,msg);
+			break;
+
+	case WEBSOCKET_PONG:
+			ESP_LOGI(TAG,"client %i responded to the ping",num);
+			break;
+    }
+}
+
+
+
+/**
+ * @brief Handle web ui websocket events.
+ */
+void websock_callback(uint8_t num, WEBSOCKET_TYPE_t type, char* msg, uint64_t len)
+{
+    switch(type) {
+	case WEBSOCKET_CONNECT:
+			ESP_LOGI(TAG,"Websocket client %i connected!",num);
+			break;
+										            
+	case WEBSOCKET_DISCONNECT_EXTERNAL:
+			ESP_LOGI(TAG,"Websocket client %i sent a disconnect message",num);
+			break;
+
+	case WEBSOCKET_DISCONNECT_INTERNAL:
+			ESP_LOGI(TAG,"Websocket client %i was disconnected",num);
+			break;
+
+	case WEBSOCKET_DISCONNECT_ERROR:
+			ESP_LOGI(TAG,"Websocket client %i was disconnected due to an error",num);
+			break;
+
+	case WEBSOCKET_TEXT:
+			ESP_LOGI(TAG,"Websocket client %i sent text message of size %i:\n%s",num,(uint32_t)len,msg);
+			dump_buf(msg, len);
+			break;
+
+	case WEBSOCKET_BIN:
+			ESP_LOGI(TAG,"Websocket client %i sent bin message of size %i:\n",num,(uint32_t)len);
+			dump_buf(msg, len);
+			break;
+
+	case WEBSOCKET_PING:
+			ESP_LOGI(TAG,"client %i pinged us with message of size %i:\n%s",num,(uint32_t)len,msg);
+			break;
+
+	case WEBSOCKET_PONG:
+			ESP_LOGI(TAG,"client %i responded to the ping",num);
+			break;
+    }
+}
+
+
+
+void dump_buf(char *buf, uint16_t buflen)
+{
+    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");
+}
+
+
+
+/**
+ * @brief Serve any client.
+ * @param http_client_sock The socket on which the client is connected.
+ */
+static void http_serve(struct netconn *conn)
+{
+    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;
+
+    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_LOGI(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;
+	    }
+	}
+
+	if (strstr(buf, "GET /logfiles.json")) {
+	    char temp[64];
+	    FILE *dest = fopen("/spiffs/w/logfiles.json", "w");
+	    if (dest) {
+	    	fprintf(dest, "{\"Dir\":[{\"Folder\":\"/log\",\"Files\":[");
+	    	DIR *dir = opendir("/sdcard/w/log");
+	    	if (dir) {
+		    struct dirent* de = readdir(dir);
+		    struct stat st;
+		    bool comma = false;
+		    while (de) {
+		    	sprintf(temp, "/sdcard/w/log/%s", de->d_name);
+		    	if (stat(temp, &st) == ESP_OK) {
+			    fprintf(dest, "%s{\"File\":\"%s\",\"Size\":%ld,\"Date\":%ld}", (comma)?",":"", de->d_name, st.st_size, st.st_mtime);
+			    comma = true;
+		    	}
+		    	de = readdir(dir);
+		    	vTaskDelay(5 / portTICK_PERIOD_MS);
+		    }
+		    closedir(dir);
+	    	}
+	    	fprintf(dest, "]}]}");
+	    	fclose(dest);
+	    }
+	    http_sendfile(conn, "/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 noVNC.
+	if ((strstr(buf,"GET /websockify ") && strstr(buf,"Upgrade: websocket"))) {
+	    int nr = ws_server_add_client_protocol(conn, buf, buflen, "/websockify", "binary", websockify_callback);
+	    ESP_LOGI(TAG, "%s new websocket on /websockify client: %d", ipstr, nr);
+	    netbuf_delete(inbuf);
+	    VncStartWS(nr);
+	    return;
+	}
+
+	// websocket for web UI.
+	if ((strstr(buf,"GET /ws ") && strstr(buf,"Upgrade: websocket"))) {
+	    int nr = ws_server_add_client_protocol(conn, buf, buflen, "/ws", "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;
+	}
+
+	dump_buf(buf, buflen);
+
+	if (strstr(buf, "GET /")) {
+		ESP_LOGI(TAG, "%s request: %s", ipstr, buf);
+		http_error(conn, 404, "Not found", "Not found");
+	} else {
+		http_error(conn, 405, "Invalid method", "Invalid method");
+	}
+    }
+
+    netconn_close(conn);
+    netconn_delete(conn);
+    netbuf_delete(inbuf);
+}
+
+
+
+/**
+ * @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, "Starting http server_task");
+    client_queue = xQueueCreate(client_queue_size,sizeof(struct netconn*));
+
+    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_LOGE(TAG, "xQueueSendToBack() queue full");	    
+	    };
+	}
+	vTaskDelay(5 / portTICK_PERIOD_MS);
+    } while (err == ERR_OK);
+
+    ESP_LOGE(TAG, "Stopping http server_task");
+    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, "Starting 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, "Starting HTTP/Websocket server");
+
+    ws_server_start();
+    xTaskCreate(&task_HTTPserver, "HTTPserver", 3000, NULL, 9, &xTaskHTTP);
+    xTaskCreate(&task_Queue,      "Queue",      4000, NULL, 6, &xTaskQueue);
+}
+

mercurial