代码可在idf的example目录下找
首先连接wifi,可以把手机当做热点,电脑和esp32同时连在手机热点,这样就可以进行通信了
void http_server_start()
{
/*挂载SPIFFS文件系统*/
const char* base_path = "/data";
MyfileSystem_mount_storage(base_path);
/*启动ap模式*/
wifi_ap_sta_start();
/*启动server*/
example_start_file_server(base_path);
}
头文件里定义file_server_data结构体作为esp32服务器端数据传输的媒介,base_path是server的文件路径,设置为/data,使用的是spiffs文件系统。scratch是数据接收和传送的缓冲区。
#include "esp_vfs.h"
/* Max length a file path can have on storage */
#define FILE_PATH_MAX 30
/* Max size of an individual file. Make sure this
* value is same as that set in upload_script.html */
#define MAX_FILE_SIZE (200*1024) // 200 KB
#define MAX_FILE_SIZE_STR "200KB"
/* Scratch buffer size */
#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];
};
下边初始化服务器,httpd_uri_match_wildcard这个代表通配符模式。允许uri通配。
httpd_handle_t server = NULL;
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
/* Use the URI wildcard matching function in order to
* allow the same handler to respond to multiple different
* target URIs which match the wildcard scheme */
config.uri_match_fn = httpd_uri_match_wildcard;
ESP_LOGI(TAG, "Starting HTTP Server on port: '%d'", config.server_port);
if (httpd_start(&server, &config) != ESP_OK) {
ESP_LOGE(TAG, "Failed to start file server!");
return ESP_FAIL;
}
初始化对页面的get请求方法,这里对所有的uri都使用download_get_handler回调函数。
/* URI handler for getting uploaded files */
httpd_uri_t file_download = {
.uri = "/*", // Match all URIs of type /path/to/file
.method = HTTP_GET,
.handler = download_get_handler,
.user_ctx = server_data // Pass server data as context
};
httpd_register_uri_handler(server, &file_download);
下边对download_get_handler函数进行说明,有些繁琐。
char filepath[FILE_PATH_MAX]; //储存的文件路径,对该服务器实例来说就是./data/xxx
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)); //文件名
这个get_path_from_uri函数就是返回一个跳过base_path的指针,具体说就是后边有些界面会返回upload/test.txt这样的uri,这个函数就会返回test.txt,也就是把前面的upload去掉了,留下了文件名方便后便解析,当然如果我们要去spiffs文件系统找肯定需要完整路径,这个完整路径就保存在dest里。返回值偏移base_pathlen把/data跳过去了。注释部分删掉对例程是没有影响的。
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); //储存的文件路径,对该服务器实例来说就是./data/xxx
size_t pathlen = strlen(uri); //传来的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);
/* Return pointer to path, skipping the base */
return dest + base_pathlen;
}
http_resp_dir_html函数给客户端返回一个响应html页面
/* If name has trailing '/', respond with directory contents */
if (filename[strlen(filename) - 1] == '/') {
return http_resp_dir_html(req, filepath); //因为要返回具体内容,具体内容存储在filepath路径下
}
通过opendir,readdir等函数对目录进行遍历,输出对应文件信息。这两行会生成超级链接。
httpd_resp_sendstr_chunk(req, "<tr><td><a href=\"");
httpd_resp_sendstr_chunk(req, req->uri);
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));
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, "<tr><td><a href=\"");
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, "</a></td><td>");
httpd_resp_sendstr_chunk(req, entrytype);
httpd_resp_sendstr_chunk(req, "</td><td>");
httpd_resp_sendstr_chunk(req, entrysize);
httpd_resp_sendstr_chunk(req, "</td><td>");
httpd_resp_sendstr_chunk(req, "<form method=\"post\" action=\"/delete");
httpd_resp_sendstr_chunk(req, req->uri);
httpd_resp_sendstr_chunk(req, entry->d_name);
httpd_resp_sendstr_chunk(req, "\"><button type=\"submit\">Delete</button></form>");
httpd_resp_sendstr_chunk(req, "</td></tr>\n");
}
这个函数中最关键的部分就是下边代码,它把嵌入到二进制文件中的html文件发送了出去。
/* 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);
http_resp_dir_html函数讲完了,继续讲download_get_handler函数。循环读取fd指向的文件并发送内容, 注意httpd_resp_sendstr_chunk函数第一次发送会自动发送请求头,不需要我们处理。这样前端页面中就会显示我们请求文件中的数据。
fd = fopen(filepath, "r");
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);
其他部分都是类似的。