前言
笔者这里有需求需要用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 服务器打印日志