源码地址
https://github.com/iafonov/multipart-parser-c
API 说明
multipart_parser_init
cpp
/**
用来初始化一个 multipart_parser 结构
boundary : 需要自行从 http 头中解析,格式为 --<boundary>,需要加上 -- 起始字符
settings :回调函数设置
**/
multipart_parser* multipart_parser_init
(const char *boundary, const multipart_parser_settings* settings)
multipart_parser_set_data
cpp
/**
用来设置用户自定义结构到 multipart_parser 上下文中,方便后续使用
p : multipart_parser 的上下文结构
data : 用户自定义结构,用来存放用户数据
**/
void multipart_parser_set_data(multipart_parser *p, void *data)
multipart_parser_get_data
cpp
/**
获取 multipart_parser_set_data 设置的自定义结构
**/
void *multipart_parser_get_data(multipart_parser *p)
multipart_parser_execute
cpp
/**
用来解析数据,可以重复调用,直到将所有数据处理完
p : multipart_parser 上下文
buf : 要解析的数据
len : 数据长度
**/
size_t multipart_parser_execute(multipart_parser* p, const char *buf, size_t len)
multipart_parser_free
cpp
/**
释放 multipart_parser_init 创建的上下文结构
**/
void multipart_parser_free(multipart_parser* p)
回调函数说明
回调结构如下,multipart_parser_execute 在解析数据时会按顺序调用各个回调函数
cpp
typedef int (*multipart_data_cb) (multipart_parser*, const char *at, size_t length);
typedef int (*multipart_notify_cb) (multipart_parser*);
struct multipart_parser_settings {
multipart_data_cb on_header_field;
multipart_data_cb on_header_value;
multipart_data_cb on_part_data;
multipart_notify_cb on_part_data_begin;
multipart_notify_cb on_headers_complete;
multipart_notify_cb on_part_data_end;
multipart_notify_cb on_body_end;
};
on_part_data_begin
每个新的 multipart 部分开始时调用,包括表单字段和文件上传。此时可以在该回调用中初始化使用 multipart_parser_set_data 设置的用户自定义结构
on_header_field
遇到头部字段名时调用,可能会被多次调用(对于较长的字段名)
on_header_value
头部字段值出现时调用,紧跟在 on_header_field 之后
on_headers_complete
一个部分的所有头部解析完成后调用,在on_part_data之前
on_part_data
处理实际数据部分时调用,可能会被多次调用(数据分块)。如果是文件上传,此时就可以开始写文件了
on_part_data_end
一个 multipart 部分完全解析完成后调用。如果是文件上传,此时可以关闭文件句柄了。
on_body_end
所有数据处理完成后调用。此时可以释放资源了。
示例
一个从服务器接收文件的示例
cpp
// 自定义结构
typedef struct
{
FILE *fp;
int is_file_type;
char save_filename[128];
char current_header[256];
} m_upload_context;
// 开始接收数据前的初始化操作
static int multipart_on_part_begin_cb(multipart_parser *parser)
{
m_upload_context *ctx = (m_upload_context *)multipart_parser_get_data(parser);
ctx->fp = NULL;
ctx->is_file_type = 0;
bzero(ctx->current_header, sizeof(ctx->current_header));
return 0;
}
// 保存 header key 值,value值在 on_header_value 中接收处理
static int multipart_on_header_field_cb(multipart_parser *parser, const char *at, size_t length)
{
m_upload_context *ctx = (m_upload_context *)multipart_parser_get_data(parser);
snprintf(ctx->current_header, MIN(sizeof(ctx->current_header), length + 1), "%s", at);
return 0;
}
// 从 Content-Disposition 值中判断该部分数据是不是文件
static int multipart_on_header_value_cb(multipart_parser *parser, const char *at, size_t length)
{
m_upload_context *ctx = (m_upload_context *)multipart_parser_get_data(parser);
if (strcasecmp(ctx->current_header, "Content-Disposition") == 0)
{
if (strstr(at, "filename=\""))
{
ctx->is_file_type = 1;
}
}
return 0;
}
// 头部解析完成,如果该部分数据是文件内容,则打开文件句柄
static int multipart_on_headers_complete_cb(multipart_parser *parser)
{
m_upload_context *ctx = (m_upload_context *)multipart_parser_get_data(parser);
if (ctx->is_file_type == 1)
{
ctx->fp = fopen(ctx->save_filename, "wb");
if (NULL == ctx->fp)
{
cgi_log_error("open file %s fail.", ctx->save_filename);
return -1;
}
}
return 0;
}
// 开始写文件
static int multipart_on_part_data_cb(multipart_parser *parser, const char *at, size_t length)
{
m_upload_context *ctx = (m_upload_context *)multipart_parser_get_data(parser);
if (ctx->fp)
{
fwrite(at, 1, length, ctx->fp);
}
return 0;
}
// 文件写结束,关闭文件句柄
static int multipart_on_part_end_cb(multipart_parser *parser)
{
m_upload_context *ctx = (m_upload_context *)multipart_parser_get_data(parser);
if (ctx->fp)
{
fclose(ctx->fp);
}
return 0;
}
int demo(char *filename)
{
int read_len = 0;
int content_len = xxx; // 报文长度自行获取
char *boundary = "--xxx"; // boundary 值自行获取
char read_buff[1024] = {0};
m_upload_context m_ctx;
multipart_parser *m_parser = NULL;
multipart_parser_settings m_setting;
m_setting.on_part_data_begin = multipart_on_part_begin_cb;
m_setting.on_header_field = multipart_on_header_field_cb;
m_setting.on_header_value = multipart_on_header_value_cb;
m_setting.on_headers_complete = multipart_on_headers_complete_cb;
m_setting.on_part_data = multipart_on_part_data_cb;
m_setting.on_part_data_end = multipart_on_part_end_cb;
snprintf(m_ctx.save_filename, sizeof(m_ctx.save_filename), "%s", filename);
m_parser = multipart_parser_init(boundary , &m_setting);
multipart_parser_set_data(m_parser, &m_ctx);
while (content_len > 0)
{
read_len = fread(read_buff, 1, 1024, stdin);
multipart_parser_execute(m_parser, read_buff, read_len );
content_len -= read_len;
}
multipart_parser_free(m_parser);
return 0
}