C/C++:mongoose.c实现多表单域文件上传

前言

笔者这里有需求需要用mongoose.c在上传文件的时候同时还要提交其他表单字段,百度一圈居然没有一个完整的例子,都是把源码示例的上传文件例子抄,讲也没讲明白。源码示例都是只有1个文件域上传。由于mongoose.c 比较轻量级,许多api也是在不断更新。为了避免大家浪费时间,先声明笔者这里用的mongoose.c 的 6.13版本,版本在mongoose.h MG_VERSION宏中有声明版本。这里笔者也把示例代码拿来改造做1个多input项的文件上传示例 做个抛砖引玉,当然也会给大家讲得明明白白,有图有真相。

讲解

需求

这里笔者只处理2个input项,第1个input项 path 指定服务器 将上传的文件存储在指定路径下,第2个为 input file。

<input name="path" type="text" value="d:\\1.txt"/>
<input name="file" type="file" />

多个文件input项以及多个普通input 道理都一样,读者可以看完 可以举一反三的。

第1步 注册处理文件上传的http接口

  /*
    上传文件接口注册
        参数1:mg_connection*
        参数2:uri,这里是/upload
        参数3:处理该请求的回调函数,当/upload这个uri请求到来,就会走handle_upload这个回调函数。
        参数4:可选参数,MG_UD_ARG是可以传指定参数的 需要在mongoose.h中开启 MG_ENABLE_CALLBACK_USERDATA 宏。
            最后一个参数有啥用呢?比如我们有多个不同业务需求(多个不同的uri)的表单form-data(一般只用在文件上传中)形式传输数据的,
            可以将代码抽象出来 所有上传接口都用1套代码,将可变的数据都放到最后1个参数指针中(比如 传输完毕后的回调函数、不同uri上传文件存储位置等)
            如果只是单纯的业务数据直接ajax发post请求即可,此种mongoose server可以参考示例restful_server。
            笔者这里只是抛转引玉,这里代码就不展开了。
  */ 
  mg_register_http_endpoint(c, "/upload", handle_upload MG_UD_ARG(NULL));

第2步 编写处理文件上传的回调函数

/*

一次form-data表单数据请求,该函数会被回调多次。具体参数含义如下:
参数1:mg_connection*
参数2:这里的ev参数,有下面5种情况,决定此次回调函数的功能,具体情况如下。
    MG_EV_HTTP_MULTIPART_REQUEST        // 表单请求开始,1次表单提交请求会有1次,可以获取请求头相关数据。
    MG_EV_HTTP_PART_BEGIN               // 1个表单input项数据传输开始,可以取到input name的值,回调n次(多少个input项回调多少次)          
    MG_EV_HTTP_PART_DATA                // 1个表单input项数据传输中,可以取到input value的值,如果是文件则为文件流,回调n*m次  
    MG_EV_HTTP_PART_END                 // 1个表单input项数据结束,回调n次(多少个input项回调多少次)    
    MG_EV_HTTP_MULTIPART_REQUEST_END    // 表单请求结束(也就是整个表单数据传输完毕),也是1次。
                                        // 可以进行数据响应操作,当然如果只是一个input file项,也可在MG_EV_HTTP_PART_END做数据响应操作。
参数3:回调函数携带的数据,与参数2有关联。
参数4:可以是mg_register_http_endpoint绑定的第4个参数,同时也是nc->user_data。

这里笔者只处理2个input项
<input name="path" type="text" value="d:\\1.txt"/>
<input name="file" type="file" />
*/
static void handle_upload(struct mg_connection *nc, int ev, void *p MG_UD_ARG(user_data))

代码

需要注意的是,需要在mongoose.h中打开http form-data文件上传功能,宏定义如下:

#define MG_ENABLE_HTTP_STREAMING_MULTIPART 1 // 开启http form-data表单上传文件

核心代码如下。完整工程可以点这里进行下载

// 自定文件上传item,这里不做复杂了
struct MyUploadItem{
    struct file_writer_data fileData;
    // 存放文件路径
    char path[MAX_PATH];
    // 原始文件名
    char fileName[MAX_PATH];
};
/*

一次form-data表单数据请求,该函数会被回调多次。具体参数含义如下:
参数1:mg_connection*
参数2:这里的ev参数,有下面5种情况,决定此次回调函数的功能,具体情况如下。
    MG_EV_HTTP_MULTIPART_REQUEST        // 表单请求开始,1次表单提交请求会有1次,可以获取请求头相关数据。
    MG_EV_HTTP_PART_BEGIN               // 1个表单input项数据传输开始,可以取到input name的值,回调n次(多少个input项回调多少次)          
    MG_EV_HTTP_PART_DATA                // 1个表单input项数据传输中,可以取到input value的值,如果是文件则为文件流,回调n*m次  
    MG_EV_HTTP_PART_END                 // 1个表单input项数据结束,回调n次(多少个input项回调多少次)    
    MG_EV_HTTP_MULTIPART_REQUEST_END    // 表单请求结束(也就是整个表单数据传输完毕),也是1次。
                                        // 可以进行数据响应操作,当然如果只是一个input file项,也可在MG_EV_HTTP_PART_END做数据响应操作。
参数3:回调函数携带的数据,与参数2有关联。
参数4:可以是mg_register_http_endpoint绑定的第4个参数,同时也是nc->user_data。

这里笔者只处理2个input项
<input name="path" type="text" value="d:\\1.txt"/>
<input name="file" type="file" />
*/
static void handle_upload(struct mg_connection *nc, int ev, void *p MG_UD_ARG(user_data)) {

    // nc携带的自定义数据
    struct MyUploadItem *upData = (struct MyUploadItem *) nc->user_data;

    // MG_EV_HTTP_MULTIPART_REQUEST 事件回调时,参数p为http_message*
    struct http_message* hm = (struct http_message*)p;

    // MG_EV_HTTP_PART_BEGIN、MG_EV_HTTP_PART_DATA、MG_EV_HTTP_PART_END 事件回调时,参数p为mg_http_multipart_part*
    struct mg_http_multipart_part *mp = (struct mg_http_multipart_part *) p;
    char pTemp[128] = {0};
    switch (ev) {
    case MG_EV_HTTP_MULTIPART_REQUEST: {
        struct mg_str* mgTemp;
        printf("======form-data Begin=======================\n");
        // 在该EV中 可获取请求头中的数据
        printf("======Request Head Data begin======\n");
        mgTemp =  mg_get_http_header(hm, "Content-Length");
        memcpy(pTemp, mgTemp->p, mgTemp->len);
        printf("Content-Length:%s \n", pTemp);
        mgTemp =  mg_get_http_header(hm, "Content-Type");
        memset(pTemp, 0, 128);
        memcpy((void*)pTemp, (void*)mgTemp->p, mgTemp->len);
        printf("Content-Type:%s \n", pTemp);
        printf("======Request Head Data end========\n");
        if(upData == NULL)
        {
            upData = (struct MyUploadItem*)calloc(1, sizeof(struct MyUploadItem));
            // 这里这样赋值后,在下次回调upload_handle函数时,第4个参数就用上了
            nc->user_data = (void *) upData;
        }
        break;
    }
    case MG_EV_HTTP_PART_BEGIN: {
        printf("======one input begin======\n");
        // 在该EV中 可以获取input name 以及 filename,filename不为空时就为input file
        if( mp->file_name != NULL && strlen(mp->file_name) > 0)
        {
            // 该input项为 input file. 此时就可以打开文件了,在MG_EV_HTTP_PART_DATA中就可以直接写数据了
            printf("input name:%s filename:%s\n", mp->var_name, mp->file_name);
            strcpy(upData->fileName, mp->file_name);
            upData->fileData.fp = fopen(upData->path, "wb+");;
            upData->fileData.bytes_written = 0;
            if (upData->fileData.fp == NULL) {
                mg_printf(nc, "%s",
                    "HTTP/1.1 500 Failed to open a file\r\n"
                    "Content-Length: 0\r\n\r\n");
                nc->flags |= MG_F_SEND_AND_CLOSE;
                return;
            }
        }
        else
        {
            // 普通input项 
            printf("input name:%s \n", mp->var_name);
        }

        break;
    }
    case MG_EV_HTTP_PART_DATA: {
        if( mp->file_name != NULL && strlen(mp->file_name) > 0)
        {
            // input file
            if (fwrite(mp->data.p, 1, mp->data.len, upData->fileData.fp) != mp->data.len) {
                mg_printf(nc, "%s",
                    "HTTP/1.1 500 Failed to write to a file\r\n"
                    "Content-Length: 0\r\n\r\n");
                nc->flags |= MG_F_SEND_AND_CLOSE;
                return;
            }
            upData->fileData.bytes_written += mp->data.len;
            printf("input name:%s filename:%s writeLen:%d \n", mp->var_name, mp->file_name, upData->fileData.bytes_written);
        }
        else if(strcmp(mp->var_name, "path") == 0)
        {
            memcpy(pTemp, mp->data.p, mp->data.len );
            // 普通input项 
            printf("input name:%s value:%s \n", mp->var_name, pTemp);
            strcpy(upData->path, pTemp);
        }
        break;
    }
    case MG_EV_HTTP_PART_END: {
        printf("======one input end======\n");
        break;
    }
    case MG_EV_HTTP_MULTIPART_REQUEST_END: {
        printf("======form-data End=======================\n\n");
        mg_printf(nc,
            "HTTP/1.1 200 OK\r\n"
            "Content-Type: text/plain\r\n"
            "Connection: close\r\n\r\n"
            "%s[len=%d] to %s success.\n\n",
            upData->fileName, (long) ftell(upData->fileData.fp), upData->path);
        nc->flags |= MG_F_SEND_AND_CLOSE;

        // 关闭文件
        fclose(upData->fileData.fp);
        // 释放calloc分配的内存
        free(upData);
        nc->user_data = NULL;
        break;
    }
  }
}

测试结果

普通表单上传测试
在这里插入图片描述
在这里插入图片描述

postman测试
在这里插入图片描述

mongoose 服务器打印日志
在这里插入图片描述

已标记关键词 清除标记
【为什么还需要学习C++?】 你是否接触很多语言,但从来没有了解过编程语言的本质? 你是否想成为一名资深开发人员,想开发别人做不了的高性能程序? 你是否经常想要窥探大型企业级开发工程的思路,但苦于没有基础只能望洋兴叹?   那么C++就是你个人能力提升,职业之路进阶的不二之选。 【课程特色】 1.课程共19大章节,239课时内容,涵盖数据结构、函数、类、指针、标准库全部知识体系。 2.带你从知识与思想的层面从0构建C++知识框架,分析大型项目实践思路,为你打下坚实的基础。 3.李宁老师结合4大国外顶级C++著作的精华为大家推出的《征服C++11》课程。 【学完后我将达到什么水平?】 1.对C++的各个知识能够熟练配置、开发、部署; 2.吊打一切关于C++的笔试面试题; 3.面向物联网的“嵌入式”和面向大型化的“分布式”开发,掌握职业钥匙,把握行业先机。 【面向人群】 1.希望一站式快速入门的C++初学者; 2.希望快速学习 C++、掌握编程要义、修炼内功的开发者; 3.有志于挑战更高级的开发项目,成为资深开发的工程师。 【课程设计】 本课程包含3大模块 基础篇 本篇主要讲解c++的基础概念,包含数据类型、运算符等基本语法,数组、指针、字符串等基本词法,循环、函数、类等基本句法等。 进阶篇 本篇主要讲解编程中常用的一些技能,包含类的高级技术、类的继承、编译链接和命名空间等。 提升篇: 本篇可以帮助学员更加高效的进行c++开发,其中包含类型转换、文件操作、异常处理、代码重用等内容。
©️2020 CSDN 皮肤主题: 酷酷鲨 设计师:CSDN官方博客 返回首页