diff -r 000000000000 -r b74b0e4902c3 main/task_http.c --- /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); +} +