esp32-idf官方例程file_serving学习

代码可在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);

其他部分都是类似的。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值