/****************************************************************************** * 作者:kerwincui * 时间:2021-06-08 * 邮箱:164770707@qq.com * 源码地址:https://gitee.com/kerwincui/wumei-smart * author: kerwincui * create: 2021-06-08 * email:164770707@qq.com * source:https://github.com/kerwincui/wumei-smart ******************************************************************************/ #include "http_server.h" #include "esp_vfs.h" #include "cJSON.h" #include "common.h" #include "nvs_storage.h" #include #include "esp_vfs.h" #include "esp_spiffs.h" #include #include #include #include #include #include "esp_err.h" #include "esp_log.h" /* 文件路径在存储上的最大长度 */ #define FILE_PATH_MAX (ESP_VFS_PATH_MAX + CONFIG_SPIFFS_OBJ_NAME_LEN) /* 单个文件的最大值。确保这个值与upload_script.html中设置的值相同 */ #define MAX_FILE_SIZE (200*1024) // 200 KB #define MAX_FILE_SIZE_STR "200KB" /* 暂存缓冲区大小 */ #define SCRATCH_BUFSIZE 8192 struct file_server_data { /* Base path of file storage */ char base_path[ESP_VFS_PATH_MAX + 1]; /* Scratch buffer for temporary storage during file transfer */ char scratch[SCRATCH_BUFSIZE]; }; static const char *TAG = "http-server"; static esp_err_t home_handler(httpd_req_t *req) { extern const unsigned char home_html_gz_start[] asm("_binary_home_html_gz_start"); extern const unsigned char home_html_gz_end[] asm("_binary_home_html_gz_end"); size_t home_html_gz_len = home_html_gz_end - home_html_gz_start; httpd_resp_set_type(req, "text/html"); httpd_resp_set_hdr(req, "Content-Encoding", "gzip"); return httpd_resp_send(req, (const char *)home_html_gz_start, home_html_gz_len); } static esp_err_t wifi_post_handler(httpd_req_t *req) { char buf[300]; int ret; int total_len = req->content_len; if (total_len >= 300) { /* Respond with 500 Internal Server Error */ httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "content too long"); return ESP_FAIL; } while (total_len > 0) { /* Read the data for the request */ if ((ret = httpd_req_recv(req, buf, MIN(total_len, sizeof(buf)))) <= 0) { if (ret == HTTPD_SOCK_ERR_TIMEOUT) { /* Retry receiving if timeout occurred */ continue; } return ESP_FAIL; } buf[total_len] = '\0'; total_len -= ret; ESP_LOGI(TAG, "=========== RECEIVED DATA =========="); ESP_LOGI(TAG, "%.*s", ret, buf); // char ssid[64],password[64],uuid[100]; sscanf(buf, "ssid=%[^&]&password=%[^&]&uuid=%s",ssid,pwd,owner_id); ESP_LOGI(TAG, "ssid:%s,password:%s,uuid:%s", ssid, pwd, owner_id); //存储wifi信息 write_ssid(); write_pwd(); write_owner_id(); //响应 extern const unsigned char prompt_html_gz_start[] asm("_binary_prompt_html_gz_start"); extern const unsigned char prompt_html_gz_end[] asm("_binary_prompt_html_gz_end"); size_t prompt_html_gz_len = prompt_html_gz_end - prompt_html_gz_start; httpd_resp_set_type(req, "text/html"); httpd_resp_set_hdr(req, "Content-Encoding", "gzip"); return httpd_resp_send(req, (const char *)prompt_html_gz_start, prompt_html_gz_len); } return ESP_FAIL; } static esp_err_t mqtt_post_handler(httpd_req_t *req) { char buf[800]; int ret, total_len = req->content_len; if (total_len >= 800) { /* Respond with 500 Internal Server Error */ httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "content too long"); return ESP_FAIL; } while (total_len > 0) { /* Read the data for the request */ if ((ret = httpd_req_recv(req, buf, MIN(total_len, sizeof(buf)))) <= 0) { if (ret == HTTPD_SOCK_ERR_TIMEOUT) { /* Retry receiving if timeout occurred */ continue; } return ESP_FAIL; } buf[total_len] = '\0'; total_len -= ret; ESP_LOGI(TAG, "=========== RECEIVED DATA =========="); ESP_LOGI(TAG, "%.*s", ret, buf); char mqtt_port[32]; int value=0; int *is_certifi=&value; sscanf(buf, "mqtt_address=%[^&]&mqtt_port=%[^&]&mqtt_account=%[^&]&mqtt_password=%[^&]&is_certifi=%d",open_broken_url,mqtt_port,open_account,open_pwd,is_certifi); is_open_certifi=*is_certifi; ESP_LOGI(TAG, "mqtt_address:%s,mqtt_port:%s,mqtt_account:%s,mqtt_password:%s,is_open_certifi:%d", open_broken_url,mqtt_port, open_account, open_pwd,is_open_certifi); //存储 is_host=1; write_is_host(); write_open_account(); write_open_pwd(); write_is_open_certifi(); // 合并mqtt的地址 strcat(open_broken_url,":"); strcat(open_broken_url,mqtt_port); printf("mqtt地址:%s",open_broken_url); write_open_broken_url(); extern const unsigned char prompt_html_gz_start[] asm("_binary_prompt_html_gz_start"); extern const unsigned char prompt_html_gz_end[] asm("_binary_prompt_html_gz_end"); size_t prompt_html_gz_len = prompt_html_gz_end - prompt_html_gz_start; httpd_resp_set_type(req, "text/html"); httpd_resp_set_hdr(req, "Content-Encoding", "gzip"); return httpd_resp_send(req, (const char *)prompt_html_gz_start, prompt_html_gz_len); } return ESP_FAIL; } /* 将传入的GET请求重定向到/index.html的处理程序可以通过上传同名文件来覆盖 */ static esp_err_t index_html_get_handler(httpd_req_t *req) { httpd_resp_set_status(req, "307 Temporary Redirect"); httpd_resp_set_hdr(req, "Location", "/download/"); httpd_resp_send(req, NULL, 0); // Response body can be empty return ESP_OK; } /* 处理程序响应一个嵌入在flash中的图标文件。浏览器期望在URI /favicon.ico获得网站图标。可以通过上传同名文件来覆盖 */ static esp_err_t favicon_get_handler(httpd_req_t *req) { extern const unsigned char favicon_ico_start[] asm("_binary_favicon_ico_start"); extern const unsigned char favicon_ico_end[] asm("_binary_favicon_ico_end"); const size_t favicon_ico_size = (favicon_ico_end - favicon_ico_start); httpd_resp_set_type(req, "image/x-icon"); httpd_resp_send(req, (const char *)favicon_ico_start, favicon_ico_size); return ESP_OK; } /* 功 能:将str字符串中的oldstr字符串替换为newstr字符串 * 参 数:str:操作目标 oldstr:被替换者 newstr:替换者 * 返回值:返回替换之后的字符串 */ char *strrpc(char *str,char *oldstr,char *newstr){ char bstr[strlen(str)];//转换缓冲区 memset(bstr,0,sizeof(bstr)); for(int i = 0;i < strlen(str);i++){ if(!strncmp(str+i,oldstr,strlen(oldstr))){//查找目标字符串 strcat(bstr,newstr); i += strlen(oldstr) - 1; }else{ strncat(bstr,str + i,1);//保存一字节进缓冲区 } } strcpy(str,bstr); return str; } /* 用运行时生成的html(包含请求路径下的所有文件和文件夹的列表)发送HTTP响应。在SPIFFS的情况下,当path是除'/'以外的任何字符串时,将返回空列表,因为SPIFFS不支持目录 */ static esp_err_t http_resp_dir_html(httpd_req_t *req, const char *dirpath) { char entrypath[FILE_PATH_MAX]; char entrysize[16]; const char *entrytype; struct dirent *entry; struct stat entry_stat; DIR *dir = opendir(dirpath); const size_t dirpath_len = strlen(dirpath); /* Retrieve the base path of file storage to construct the full path */ strlcpy(entrypath, dirpath, sizeof(entrypath)); if (!dir) { ESP_LOGE(TAG, "Failed to stat dir : %s", dirpath); /* Respond with 404 Not Found */ httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "Directory does not exist"); return ESP_FAIL; } /* Send HTML file header */ httpd_resp_sendstr_chunk(req, "物美智能设备AP配置"); /* Get handle to embedded file upload script */ extern const unsigned char upload_script_start[] asm("_binary_upload_script_html_start"); extern const unsigned char upload_script_end[] asm("_binary_upload_script_html_end"); const size_t upload_script_size = (upload_script_end - upload_script_start); /* Add file upload form and script which on execution sends a POST request to /upload */ httpd_resp_send_chunk(req, (const char *)upload_script_start, upload_script_size); /* Send file-list table definition and column labels */ httpd_resp_sendstr_chunk(req, "
" "" ""); /* Iterate over all files / folders and fetch their names and sizes */ while ((entry = readdir(dir)) != NULL) { entrytype = (entry->d_type == DT_DIR ? "directory" : "file"); strlcpy(entrypath + dirpath_len, entry->d_name, sizeof(entrypath) - dirpath_len); if (stat(entrypath, &entry_stat) == -1) { ESP_LOGE(TAG, "Failed to stat %s : %s", entrytype, entry->d_name); continue; } sprintf(entrysize, "%ld", entry_stat.st_size); ESP_LOGI(TAG, "Found %s : %s (%s bytes)", entrytype, entry->d_name, entrysize); /* Send chunk of HTML file containing table entries with file name and size */ // httpd_resp_sendstr_chunk(req, "\n"); } closedir(dir); /* Finish the file list table */ httpd_resp_sendstr_chunk(req, "
名称类型字节操作
"); // httpd_resp_sendstr_chunk(req, req->uri); // httpd_resp_sendstr_chunk(req, entry->d_name); // if (entry->d_type == DT_DIR) { // httpd_resp_sendstr_chunk(req, "/"); // } // httpd_resp_sendstr_chunk(req, "\">"); httpd_resp_sendstr_chunk(req, entry->d_name); httpd_resp_sendstr_chunk(req, ""); // httpd_resp_sendstr_chunk(req, ""); httpd_resp_sendstr_chunk(req, entrytype); httpd_resp_sendstr_chunk(req, ""); httpd_resp_sendstr_chunk(req, entrysize); httpd_resp_sendstr_chunk(req, ""); httpd_resp_sendstr_chunk(req, "
uri); httpd_resp_sendstr_chunk(req, entry->d_name); httpd_resp_sendstr_chunk(req, "\">
"); httpd_resp_sendstr_chunk(req, "
"); /* Send remaining chunk of HTML file to complete it */ httpd_resp_sendstr_chunk(req, ""); /* Send empty chunk to signal HTTP response completion */ httpd_resp_sendstr_chunk(req, NULL); return ESP_OK; } #define IS_FILE_EXT(filename, ext) \ (strcasecmp(&filename[strlen(filename) - sizeof(ext) + 1], ext) == 0) /* 根据文件扩展设置HTTP响应内容类型 */ static esp_err_t set_content_type_from_file(httpd_req_t *req, const char *filename) { if (IS_FILE_EXT(filename, ".pdf")) { return httpd_resp_set_type(req, "application/pdf"); } else if (IS_FILE_EXT(filename, ".html")) { return httpd_resp_set_type(req, "text/html"); } else if (IS_FILE_EXT(filename, ".jpeg")) { return httpd_resp_set_type(req, "image/jpeg"); } else if (IS_FILE_EXT(filename, ".ico")) { return httpd_resp_set_type(req, "image/x-icon"); } else if (IS_FILE_EXT(filename, ".crt")) { return httpd_resp_set_type(req, "application/x-x509-ca-cert"); } /* This is a limited set only */ /* For any other type always set as plain text */ return httpd_resp_set_type(req, "text/plain"); } /*将完整路径复制到目标缓冲区并返回指向路径的指针(跳过前面的基路径) */ static const char* get_path_from_uri(char *dest, const char *base_path, const char *uri, size_t destsize) { const size_t base_pathlen = strlen(base_path); size_t pathlen = strlen(uri); const char *quest = strchr(uri, '?'); if (quest) { pathlen = MIN(pathlen, quest - uri); } const char *hash = strchr(uri, '#'); if (hash) { pathlen = MIN(pathlen, hash - uri); } if (base_pathlen + pathlen + 1 > destsize) { /* Full path string won't fit into destination buffer */ return NULL; } /* Construct full path (base + path) */ strcpy(dest, base_path); strlcpy(dest + base_pathlen, uri, pathlen + 1); //删除路径中的download字符串 strrpc(dest,"/download",""); /* Return pointer to path, skipping the base */ return dest + base_pathlen; } /* 下载服务器里面的文件 */ static esp_err_t download_get_handler(httpd_req_t *req) { char filepath[FILE_PATH_MAX]; FILE *fd = NULL; struct stat file_stat; const char *filename = get_path_from_uri(filepath, ((struct file_server_data *)req->user_ctx)->base_path, req->uri, sizeof(filepath)); if (!filename) { ESP_LOGE(TAG, "Filename is too long"); /* Respond with 500 Internal Server Error */ httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Filename too long"); return ESP_FAIL; } /* If name has trailing '/', respond with directory contents */ if (filename[strlen(filename) - 1] == '/') { return http_resp_dir_html(req, filepath); } if (stat(filepath, &file_stat) == -1) { /* If file not present on SPIFFS check if URI * corresponds to one of the hardcoded paths */ if (strcmp(filename, "/index.html") == 0) { return index_html_get_handler(req); } else if (strcmp(filename, "/favicon.ico") == 0) { return favicon_get_handler(req); } ESP_LOGE(TAG, "Failed to stat file : %s", filepath); /* Respond with 404 Not Found */ httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "File does not exist"); return ESP_FAIL; } fd = fopen(filepath, "r"); if (!fd) { ESP_LOGE(TAG, "Failed to read existing file : %s", filepath); /* Respond with 500 Internal Server Error */ httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to read existing file"); return ESP_FAIL; } ESP_LOGI(TAG, "Sending file : %s (%ld bytes)...", filename, file_stat.st_size); set_content_type_from_file(req, filename); /* Retrieve the pointer to scratch buffer for temporary storage */ char *chunk = ((struct file_server_data *)req->user_ctx)->scratch; size_t chunksize; do { /* Read file in chunks into the scratch buffer */ chunksize = fread(chunk, 1, SCRATCH_BUFSIZE, fd); if (chunksize > 0) { /* Send the buffer contents as HTTP response chunk */ if (httpd_resp_send_chunk(req, chunk, chunksize) != ESP_OK) { fclose(fd); ESP_LOGE(TAG, "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; } } /* Keep looping till the whole file is sent */ } while (chunksize != 0); /* Close file after sending complete */ fclose(fd); ESP_LOGI(TAG, "File sending complete"); /* Respond with an empty chunk to signal HTTP response completion */ httpd_resp_send_chunk(req, NULL, 0); return ESP_OK; } /*上传文件到服务器 */ static esp_err_t upload_post_handler(httpd_req_t *req) { char filepath[FILE_PATH_MAX]; FILE *fd = NULL; struct stat file_stat; /* Skip leading "/upload" from URI to get filename */ /* Note sizeof() counts NULL termination hence the -1 */ const char *filename = get_path_from_uri(filepath, ((struct file_server_data *)req->user_ctx)->base_path, req->uri + sizeof("/upload") - 1, sizeof(filepath)); if (!filename) { /* Respond with 500 Internal Server Error */ httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Filename too long"); return ESP_FAIL; } /* Filename cannot have a trailing '/' */ if (filename[strlen(filename) - 1] == '/') { ESP_LOGE(TAG, "Invalid filename : %s", filename); httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Invalid filename"); return ESP_FAIL; } if (stat(filepath, &file_stat) == 0) { ESP_LOGE(TAG, "File already exists : %s", filepath); //删除文件,并重新上传 unlink(filepath); /* Respond with 400 Bad Request */ httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "文件已经存在,将会被替换"); // return ESP_FAIL; } /* File cannot be larger than a limit */ if (req->content_len > MAX_FILE_SIZE) { ESP_LOGE(TAG, "File too large : %d bytes", req->content_len); /* Respond with 400 Bad Request */ httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "File size must be less than " MAX_FILE_SIZE_STR "!"); /* Return failure to close underlying connection else the * incoming file content will keep the socket busy */ return ESP_FAIL; } fd = fopen(filepath, "w"); if (!fd) { ESP_LOGE(TAG, "Failed to create file : %s", filepath); /* Respond with 500 Internal Server Error */ httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to create file"); return ESP_FAIL; } ESP_LOGI(TAG, "Receiving file : %s...", filename); /* Retrieve the pointer to scratch buffer for temporary storage */ char *buf = ((struct file_server_data *)req->user_ctx)->scratch; int received; /* Content length of the request gives * the size of the file being uploaded */ int remaining = req->content_len; while (remaining > 0) { ESP_LOGI(TAG, "Remaining size : %d", remaining); /* Receive the file part by part into a buffer */ if ((received = httpd_req_recv(req, buf, MIN(remaining, SCRATCH_BUFSIZE))) <= 0) { if (received == HTTPD_SOCK_ERR_TIMEOUT) { /* Retry if timeout occurred */ continue; } /* In case of unrecoverable error, * close and delete the unfinished file*/ fclose(fd); unlink(filepath); ESP_LOGE(TAG, "File reception failed!"); /* Respond with 500 Internal Server Error */ httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to receive file"); return ESP_FAIL; } /* Write buffer content to file on storage */ if (received && (received != fwrite(buf, 1, received, fd))) { /* Couldn't write everything to file! * Storage may be full? */ fclose(fd); unlink(filepath); ESP_LOGE(TAG, "File write failed!"); /* Respond with 500 Internal Server Error */ httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to write file to storage"); return ESP_FAIL; } /* Keep track of remaining size of * the file left to be uploaded */ remaining -= received; } /* Close file upon upload completion */ fclose(fd); ESP_LOGI(TAG, "File reception complete"); httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "文件上传成功"); /* Redirect onto root to see the updated file list */ // httpd_resp_set_status(req, "303 See Other"); // httpd_resp_set_hdr(req, "Location", "/download/"); // httpd_resp_sendstr(req, "File uploaded successfully"); return ESP_OK; } /* 服务器删除文件 */ static esp_err_t delete_post_handler(httpd_req_t *req) { char filepath[FILE_PATH_MAX]; struct stat file_stat; /* Skip leading "/delete" from URI to get filename */ /* Note sizeof() counts NULL termination hence the -1 */ const char *filename = get_path_from_uri(filepath, ((struct file_server_data *)req->user_ctx)->base_path, req->uri + sizeof("/delete") - 1, sizeof(filepath)); if (!filename) { /* Respond with 500 Internal Server Error */ httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Filename too long"); return ESP_FAIL; } /* Filename cannot have a trailing '/' */ if (filename[strlen(filename) - 1] == '/') { ESP_LOGE(TAG, "Invalid filename : %s", filename); httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Invalid filename"); return ESP_FAIL; } if (stat(filepath, &file_stat) == -1) { ESP_LOGE(TAG, "File does not exist : %s", filename); /* Respond with 400 Bad Request */ httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "File does not exist"); return ESP_FAIL; } ESP_LOGI(TAG, "Deleting file : %s", filename); /* 删除文件 */ unlink(filepath); /* Redirect onto root to see the updated file list */ httpd_resp_set_status(req, "303 See Other"); httpd_resp_set_hdr(req, "Location", "/download/"); httpd_resp_sendstr(req, "File deleted successfully"); return ESP_OK; } static esp_err_t start_webserver(void) { static struct file_server_data *server_data = NULL; httpd_handle_t server = NULL; httpd_config_t config = HTTPD_DEFAULT_CONFIG(); if (server_data) { ESP_LOGE(TAG, "File server already started"); return ESP_ERR_INVALID_STATE; } /* 为服务器数据分配内存 */ server_data = calloc(1, sizeof(struct file_server_data)); if (!server_data) { ESP_LOGE(TAG, "Failed to allocate memory for server data"); return ESP_ERR_NO_MEM; } strlcpy(server_data->base_path, "/spiffs",sizeof(server_data->base_path)); /* 使用URI通配符匹配函数允许同一处理程序响应多个与通配符方案匹配的不同目标URI */ config.uri_match_fn = httpd_uri_match_wildcard; ESP_LOGI(TAG, "Starting HTTP Server"); if (httpd_start(&server, &config) != ESP_OK) { ESP_LOGE(TAG, "Failed to start server!"); return ESP_FAIL; } httpd_uri_t wifi = { .uri = "/wifi", .method = HTTP_POST, .handler = wifi_post_handler, .user_ctx = NULL }; httpd_uri_t mqtt = { .uri = "/mqtt", .method = HTTP_POST, .handler = mqtt_post_handler, .user_ctx = NULL }; httpd_uri_t home = { .uri = "/", .method = HTTP_GET, .handler = home_handler, .user_ctx = NULL }; httpd_uri_t file_download = { .uri = "/download/*", .method = HTTP_GET, .handler = download_get_handler, .user_ctx = server_data // Pass server data as context }; httpd_uri_t file_upload = { .uri = "/upload/*", // Match all URIs of type /upload/path/to/file .method = HTTP_POST, .handler = upload_post_handler, .user_ctx = server_data }; httpd_uri_t file_delete = { .uri = "/delete/*", .method = HTTP_POST, .handler = delete_post_handler, .user_ctx = server_data }; // Set URI handlers ESP_LOGI(TAG, "Registering URI handlers"); httpd_register_uri_handler(server, &home); httpd_register_uri_handler(server, &wifi); httpd_register_uri_handler(server, &mqtt); httpd_register_uri_handler(server, &file_download); httpd_register_uri_handler(server, &file_upload); httpd_register_uri_handler(server, &file_delete); return ESP_OK; } static void stop_webserver(httpd_handle_t server) { // Stop the httpd server httpd_stop(server); } // 初始化spiffs static esp_err_t init_spiffs(void) { ESP_LOGI(TAG, "Initializing SPIFFS"); esp_vfs_spiffs_conf_t conf = { .base_path = "/spiffs", .partition_label = NULL, .max_files = 5, // This decides the maximum number of files that can be created on the storage .format_if_mount_failed = true }; esp_err_t ret = esp_vfs_spiffs_register(&conf); if (ret != ESP_OK) { if (ret == ESP_FAIL) { ESP_LOGE(TAG, "Failed to mount or format filesystem"); } else if (ret == ESP_ERR_NOT_FOUND) { ESP_LOGE(TAG, "Failed to find SPIFFS partition"); } else { ESP_LOGE(TAG, "Failed to initialize SPIFFS (%s)", esp_err_to_name(ret)); } return ESP_FAIL; } size_t total = 0, used = 0; ret = esp_spiffs_info(NULL, &total, &used); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to get SPIFFS partition information (%s)", esp_err_to_name(ret)); return ESP_FAIL; } ESP_LOGI(TAG, "Partition size: total: %d, used: %d", total, used); return ESP_OK; } void http_server_start(void) { ESP_ERROR_CHECK(init_spiffs()); /* Start the server for the first time */ ESP_ERROR_CHECK(start_webserver()); }