Wed, 24 Oct 2018 11:55:39 +0200
Removed old logfiles
/** * @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" 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; /** * @brief Debug dump buffer * @param buf The buffer * @param buflen Length of the buffer */ 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); }