HTTP 的 multipart 类型

news2025/2/24 14:00:06

        上一篇文章讲到 http 的 MIME 类型 http MIME 类型 里有一个 multipart 多部分对象集合类型,这个类型 http 指南里有讲到:MIME 中的 multipart(多部分)电子邮件报文中包含多个报文,它们合在一起作为单一的复杂报文发送。每一部分都是独立的,有各自的描述及内容的集;不同的部分之间用分界字符串连接在一起。HTTP 也支持多部分主体,不过,通常只用在下列两种情形之一:提交填写好的表格,或是作为承载若干文档片段的范围响应。

        前端技术不懂,这里只用 postman 用为客户端来做示例。表格和文档形式,是不是像这样的呢?

当你选中其中一种情形时,http 的 Headers 里就会多出一个 Content-Type 表明这是一个多部分集合的类型报文:

表格情形还没试验过,这里主要讲文档情形的。所以呢,用客户端 postman 可以向服务端发送大的或是小的文档。下面给出服务端的例子:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <string>
#include <map>
#include <mutex>
#include <sstream>
#include <iostream>
#include "mongoose.h"
#include "../logFormatPrt/log.h"

#define FILE_NAME_LEN 128
#define FILE_PATH_LEN 32

void eventHandler(struct mg_connection *nc, int event, void *eventData);
void fileUpload(mg_connection* nc, const int ev, void* data);
bool validPath(const char *path);

struct userData
{
    int index;
};

struct FileInfo
{
    FILE *fp; //打开新文件的指针
    char fileName[FILE_NAME_LEN]; //文件名,包含路径
    char filePath[FILE_PATH_LEN]; //文件路径
    size_t size; //文件大小,暂时没有用到
    size_t byteWrite;//已经写的字节数
};


//用postman 测试,linux需要关闭防火墙,否则收不到数据
int main(int argc, char *argv[])
{   
    struct mg_mgr mgr;
 
    mg_mgr_init(&mgr, nullptr);
 
    int port = 8190;
    char buf[5] = {0};
    snprintf(buf, sizeof(buf), "%d", port);
    struct mg_connection *con = mg_bind(&mgr, buf, nullptr);
 
    if(con == NULL) {
        errorf("mg_bind fail\n");
        return -1;
    }
 
    mg_set_protocol_http_websocket(con);
    infof("listen ip[%s], port[%d]....\n", inet_ntoa(con->sa.sin.sin_addr), port); 

    //uri是/fileUpload 时调用函数fileUpload
    mg_register_http_endpoint(con, "/fileUpload", fileUpload);

    while (1)
    {
        mg_mgr_poll(&mgr, 100);
    }
    
    mg_mgr_free(&mgr);
    return 0;
}


//触发的事件依次为:
//#define MG_EV_HTTP_MULTIPART_REQUEST 121 /* struct http_message */
//#define MG_EV_HTTP_PART_BEGIN 122        /* struct mg_http_multipart_part */
//#define MG_EV_HTTP_PART_DATA 123         /* struct mg_http_multipart_part */
//#define MG_EV_HTTP_PART_END 124          /* struct mg_http_multipart_part */
/* struct mg_http_multipart_part */
//#define MG_EV_HTTP_MULTIPART_REQUEST_END 125

void fileUpload(mg_connection* nc, const int ev, void* data)
{
    //用户指针,用于保存文件大小,文件名
    struct FileInfo *userData = nullptr;
 
    //当事件ev是 MG_EV_HTTP_MULTIPART_REQUEST 时,data类型是http_message
    struct http_message *httpMsg = nullptr;
    if(MG_EV_HTTP_MULTIPART_REQUEST == ev)
    {
        httpMsg = (struct http_message*)data;
        //初次请求时,申请内存
        if(userData == nullptr)
        {
            userData = (struct FileInfo *)malloc(sizeof(struct FileInfo));
            memset(userData, 0, sizeof(struct FileInfo));
        }
    }
    else // 已经不是第一次请求了,nc->user_data 先前已经指向 userData,所以可以用了
    {
        userData = (struct FileInfo *)nc->user_data;
    }
 
    //当事件ev是 MG_EV_HTTP_PART_BEGIN/MG_EV_HTTP_PART_DATA/MG_EV_HTTP_PART_END 时,data类型是mg_http_multipart_part
    struct mg_http_multipart_part *httpMulMsg = nullptr;
    if(ev >= MG_EV_HTTP_PART_BEGIN && ev <= MG_EV_HTTP_PART_END)
    {
        httpMulMsg = (struct mg_http_multipart_part*)data;
    }

    switch(ev) 
    {
        case MG_EV_HTTP_MULTIPART_REQUEST:
            {   
                ///query_string 为请求地址中的变量, key 名称约定好
                char filePath[32] = {0};
                std::string key("filePath");

                //从请求地址里获取 key 对应的值,所以这个需要和请求地址里的 key 一样
                //这里从地址中获取文件要上传到哪个路径
                if(mg_get_http_var(&httpMsg->query_string, key.c_str(), filePath, sizeof(filePath)) > 0) 
                {
                    tracef("upload file request, locate: %s = %s\n", key.c_str(), filePath); 
                }

                if(!validPath(filePath))
                {
                    tracef("no such directory of %s\n", filePath);
                    std::string header;
                    std::string body("no suce directory");
                    header.append("HTTP/1.1 500 file fail").append("\r\n");
                    header.append("Connection: close").append("\r\n");
                    header.append("Content-Length: ").append(std::to_string(body.length())).append("\r\n").append("\r\n");
                    header.append(body).append("\r\n");

                    mg_send(nc, header.c_str(), header.length());
                    nc->flags |= MG_F_SEND_AND_CLOSE;             
                }

                //保存路径,且 nc->user_data 指向该内存,下次请求就可以直接用了
                if(userData != nullptr)
                {
                    snprintf(userData->filePath, sizeof(userData->filePath), "%s", filePath);
                    nc->user_data = (void *)userData;                 
                }
            }
 
            break;
        case MG_EV_HTTP_PART_BEGIN:  ///这一步获取文件名
            tracef("upload file begin!\n");
            if(httpMulMsg->file_name != NULL && strlen(httpMulMsg->file_name) > 0)
            {
                tracef("input fileName = %s\n", httpMulMsg->file_name);
                //保存文件名,且新建一个文件,支持目录带 "/" 及不带 "/"
                if(userData != nullptr)
                {
                    if(userData->filePath[strlen(userData->filePath)] == '/')
                    {
                        snprintf(userData->fileName, sizeof(userData->fileName), "%s%s", userData->filePath, httpMulMsg->file_name);
                    }
                    else
                    {
                        snprintf(userData->fileName, sizeof(userData->fileName), "%s/%s", userData->filePath, httpMulMsg->file_name);
                    }
                    
                    userData->fp = fopen(userData->fileName, "wb+");

                    //创建文件失败,回复,释放内存
                    if(userData->fp == NULL) 
                    {
                        mg_printf(nc, "%s", 
                            "HTTP/1.1 500 file fail\r\n"
                            "Content-Length: 25\r\n"
                            "Connection: close\r\n\r\n"
                            "Failed to open a file\r\n");

                        nc->flags |= MG_F_SEND_AND_CLOSE;
                        free(userData);
                        nc->user_data = nullptr;     
                        return;
                    }                    
                }

            }
            break;
        case MG_EV_HTTP_PART_DATA: //这一步写文件
            //tracef("upload file chunk size = %lu\n", httpMulMsg->data.len);
            if(userData != nullptr && userData->fp != NULL) 
            {
                size_t ret = fwrite(httpMulMsg->data.p, 1, httpMulMsg->data.len, userData->fp);
                if(ret != httpMulMsg->data.len)
                {
                    mg_printf(nc, "%s",
                    "HTTP/1.1 500 write fail\r\n"
                    "Content-Length: 29\r\n\r\n"
                    "Failed to write to a file\r\n");

                    nc->flags |= MG_F_SEND_AND_CLOSE;
                    return;
                }
                userData->byteWrite += ret;  
            }
            break;
        case MG_EV_HTTP_PART_END:
            tracef("file transfer end!\n");
            if(userData != NULL && userData->fp != NULL)
            {
                mg_printf(nc,
                    "HTTP/1.1 200 OK\r\n"
                    "Content-Type: text/plain\r\n"
                    "Connection: close\r\n\r\n"
                    "Written %lu bytes of POST data to a file\n\n",
                    userData->byteWrite);

                //设置标志,发送完成数据(如果有)并且关闭连接
                nc->flags |= MG_F_SEND_AND_CLOSE;
                
                //关闭文件,释放内存
                fclose(userData->fp);
                tracef("upload file end, free userData(%p)\n", userData);
                free(userData);
                nc->user_data = NULL;       
            } 
            else
            {
                mg_printf(nc,
                    "HTTP/1.1 200 OK\r\n"
                    "Content-Type: text/plain\r\n"
                    "Connection: close\r\n\r\n"
                    "Written 0 of POST data to a file\n\n");                
            }       
            break;
        case MG_EV_HTTP_MULTIPART_REQUEST_END:
            tracef("http multipart request end!\n");
            break;
        default:
            break;
    }
}

bool validPath(const char *path)
{
    struct stat st;
    if(lstat(path, &st) == 0)
    {
        return true;
    }
    return false;
}

如果想要直接编译则需要把头文件 #include "../logFormatPrt/log.h" 去掉,编译选项还得加上 -lssl -lcrypto,Makefile 如下:

#中间文件存放目录,如.o 和 .d 文件
COMPILE_DIR = compile
BIN_DIR = bin

# 可编译arm版本
#CROSS = arm-himix200-linux-
CC = gcc -m32
CPP = $(CROSS)g++ -std=c++11 -m32
CFLAGS = -Werror -g

LIB = -lpthread -lssl -lcrypto
#CPP_SRCS = $(wildcard *.cpp)
CPP_SRCS = $(shell ls -t | grep "\.cpp$$" | head -1)
CPP_OBJS = $(patsubst %.cpp, $(COMPILE_DIR)/%.o, $(CPP_SRCS))
CPP_DEP = $(patsubst %.cpp, $(COMPILE_DIR)/%.cpp.d, $(CPP_SRCS))

C_SRCS = mongoose.c
C_OBJS = $(patsubst %.c, $(COMPILE_DIR)/%.o, $(C_SRCS))
C_DEP = $(patsubst %.c, $(COMPILE_DIR)/%.c.d, $(C_SRCS))

OBJS = $(CPP_OBJS) $(C_OBJS)
DEP_ALL = $(CPP_DEP) $(C_DEP)

$(shell if [ ! -d $(COMPILE_DIR) ]; then mkdir $(COMPILE_DIR); fi)
$(shell if [ ! -d $(BIN_DIR) ]; then mkdir $(BIN_DIR); fi)


BIN =
ifeq ($(target), ) #如果是空的
BIN = a.out
else
BIN := $(target)
endif


TARGET=$(BIN_DIR)/$(BIN)


all: $(TARGET)

-include $(DEP_ALL)

$(TARGET): $(OBJS)
	$(CPP) $(CFLAGS) $^ -o $@ $(LIB)


$(COMPILE_DIR)/%.o: %.cpp $(COMPILE_DIR)/%.cpp.d
	$(CPP) $(CFLAGS) -c $< -o $@

$(COMPILE_DIR)/%.cpp.d: %.cpp
	$(CPP) $(CFLAGS) -MM -E -c $< -o $@
	@sed 's/.*\.o/$(subst /,\/,$(dir $@))&/g' $@ > $@.tmp
	@mv $@.tmp $@

$(COMPILE_DIR)/%.o: %.c $(COMPILE_DIR)/%.c.d
	$(CC) $(CFLAGS) -c $< -o $@

$(COMPILE_DIR)/%.c.d: %.c
	$(CC) $(CFLAGS) -MM -E -c $< -o $@
	@sed 's/.*\.o/$(subst /,\/,$(dir $@))&/g' $@ > $@.tmp
	@mv $@.tmp $@

.PHONY: clean
clean:
	rm -rf $(COMPILE_DIR) $(BIN_DIR)

大到一个G的文件,上传也是没有问题的,1.3G 的文件:

如果问我客户端怎么写,这个我是不会的,但 postman 里有个“代码片段”选项,可以翻译成不同的编码语言的代码:

而在 Mongoose.c 源码里,是这样处理 multipart 类型的报文的,判断头部字段 Content-Type 为 multipart 时,交由相关处理函数进行处理,然后就 return 了,不再后续处理了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1474831.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

System Verilog 要点概览

二进制 常量 格式&#xff1a;二进制位宽进制符号&#xff08;b:2;h:16;d:10&#xff09;数据 1b1 1b0 16habcd 4d10变量 logic a;//一位二进制 logic [3:0]b;//4位二进制 logic [31:0][31:0]c;//32*32位二进制组合/位绑定 {a, 1b1}//高位是a&#xff0c;低位是常数二进制…

go test用法(获取单元测试覆盖率)

go test用法&#xff08;获取ut覆盖率&#xff09; 为了提升系统的稳定性&#xff0c;一般公司都会对代码的单元测试覆盖率有一定要求。下面针对golang自带的测试命令go test做讲解。 1 命令 1.1 go test ./… &#xff08;运行当前目录及所有子目录下的测试用例&#xff09; …

读《Shape-Guided: Shape-Guided Dual-Memory Learning for 3D Anomaly Detection》

Chu Y M, Chieh L, Hsieh T I, et al. Shape-Guided Dual-Memory Learning for 3D Anomaly Detection[J]. 2023.&#xff08;为毛paperwithcode上面曾经的榜一引用却只有1&#xff09; 摘要 专家学习 无监督 第一个专家&#xff1a;局部几何&#xff0c;距离建模 第二个专家&…

【React源码 - 调度任务循环EventLoop】

我们知道在React中有4个核心包、2个关键循环。而React正是在这4个核心包中运行&#xff0c;从输入到输出渲染到web端&#xff0c;主要流程可简单分为一下4步&#xff1a;如下图&#xff0c;本文主要是介绍两大循环中的任务调度循环。 4个核心包&#xff1a; react&#xff1a;…

数据结构 队列

一定义 1.1概述&#xff1a; 队列只允许在一端进行插入操作&#xff0c;而在另一端进行删除操作的线性表 特点&#xff1a;队列是先进先出的线性表 允许插入的一端称为队尾&#xff0c;允许删除的一端是队头 这里我们就介绍链式的 1.2 建立队列 这里说一句 其实不管是栈还…

php基础学习之错误处理(其一)

一&#xff0c;错误处理的概念 错误处理指的是系统(或者用户)在执行某些代码的时候&#xff0c;发现有错误&#xff0c;就会通过错误处理的形式告知程序员&#xff0c;俗称报错 二&#xff0c;错误分类 语法错误&#xff1a;书写的代码不符合 PHP 的语法规范&#xff0c;语法错…

【医学影像】LIDC-IDRI数据集的无痛制作

LIDC-IDRI数据集制作 0.下载0.0 链接汇总0.1 步骤 1.合成CT图reference 0.下载 0.0 链接汇总 LIDC-IDRI官方网址&#xff1a;https://www.cancerimagingarchive.net/nbia-search/?CollectionCriteriaLIDC-IDRINBIA Data Retriever 下载链接&#xff1a;https://wiki.canceri…

基于springboot+vue的编程训练系统(前后端分离)

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战&#xff0c;欢迎高校老师\讲师\同行交流合作 ​主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Pyt…

斯元Z-ONE-China Cybersecurity Tech Landscape·中国网络安全全景图-百度网盘下载

面向全球&#xff0c;斯元Z-ONE正式发布首版「China Cybersecurity Tech Landscape中国网络安全全景图」。 为了提升海外市场对中国网络安全行业的全局认识&#xff0c;方便国际客户及合作伙伴了解中国网络安全科技的赛道分布和国内外厂商对标&#xff0c;助力中国网安厂商出海…

Qt中tableView控件的使用

tableView使用注意事项 tableView在使用时&#xff0c;从工具栏拖动到底层页面后&#xff0c;右键进行选择如下图所示&#xff1a; 此处需要注意的是&#xff0c;需要去修改属性&#xff0c;从UI上修改属性如下所示&#xff1a; 也可以通过代码修改属性&#xff1a; //将其设…

个人玩航拍,如何申请无人机空域?

我们在《年会不能停》一文中&#xff0c;有分享我们在西岭雪山用无人机拍摄的照片和视频&#xff0c;有兴趣可以去回顾。 春节的时候&#xff0c;趁着回老家一趟&#xff0c;又将无人机带了回去&#xff0c;计划拍一下老家的风景。 原本以为穷乡僻壤的地方可以随便飞&#xf…

【c语言】内存函数

欢迎关注个人主页&#xff1a;逸狼 创造不易&#xff0c;可以点点赞吗~ 如有错误&#xff0c;欢迎指出~ 目录 memcpy函数的使用和模拟实现 memcpy函数的使用 memcpy函数的模拟实现 memmove的使用和模拟实现 memmove的使用 memmove的模拟实现 memset函数的使用 memcmp函数…

【Docker】安装及相关的命令

目录 一 Docker简介 1.1 是什么 1.2 优缺点 1.3 应用场景 1.4 安装 二 命令 2.1 Docker基本命令 2.2 Docker镜像命令 2.3 Docker容器命令 一 Docker简介 1.1 是什么 Docker是一个开源的应用容器引擎&#xff0c;它基于Go语言实现&#xff0c;并利用操作系统本身已有的…

Kafka安全模式之身份认证

一、简介 Kafka作为一个分布式的发布-订阅消息系统&#xff0c;在日常项目中被频繁使用&#xff0c;通常情况下无论是生产者还是消费者只要订阅Topic后&#xff0c;即可进行消息的发送和接收。而kafka在0.9.0.0版本后添加了身份认证和权限控制两种安全服务&#xff0c;本文主要…

六、防御保护---防火墙内容安全篇

六、防御保护---防火墙内容安全篇 一、IAE&#xff08;Intelligent Awareness Engine&#xff09;引擎二、深度检测技术(DFI和DPI&#xff09;2.1 DPI -- 深度包检测技术2.1.1 基于“特征字”的检测技术2.1.2 基于应用网关的检测技术2.1.3 基于行为模式的检测技术 2.2 DFI -- 深…

CGI程序与ShellShock漏洞

CGI是什么&#xff1f; CGI&#xff08;通用网关接口&#xff0c;Common Gateway Interface&#xff09;程序是一种用于在Web服务器上执行动态内容的技术。与服务器上普通的后端代码相比&#xff0c;CGI程序有几个区别&#xff1a; 执行环境&#xff1a; CGI程序在服务器上作为…

k8s部署java微服务程序时,关于配置conusl acl token的方法总结

一、背景 java微服务程序使用consul作为服务注册中心&#xff0c;而consul集群本身的访问是需要acl token的&#xff0c;以增强服务调用的安全性。 本文试着总结下&#xff0c;有哪些方法可以配置consul acl token&#xff0c;便于你根据具体的情况选择。 个人认为&#xff…

BL0942 内置时钟免校准计量芯片 用于智能家居领域 低成本

BL0939是上海贝岭股份有限公司开发的一款用于智能家居领域进行电能测量的专用芯片&#xff0c;支持两路测量&#xff0c;可同时进行计量和漏电故障检测&#xff0c;漏电检测电流可设&#xff0c;响应时间快&#xff0c;具有体积小&#xff0c;外围电路简单&#xff0c;成本低廉…

C++ 前缀和

目录 1、DP34 【模板】前缀和 2、DP35 【模板】二维前缀和​编辑 3、724. 寻找数组的中心下标 4、238. 除自身以外数组的乘积 5、560. 和为 K 的子数组 6、974. 和可被 K 整除的子数组 7、525. 连续数组 8、1314. 矩阵区域和 1、DP34 【模板】前缀和 思路&#xff1a;…

Project_Euler-11 题解

Project_Euler-11 题解 题目 题目中给出的数据如下&#xff1a; 08 02 22 97 38 15 00 40 00 75 04 05 07 78 52 12 50 77 91 08 49 49 99 40 17 81 18 57 60 87 17 40 98 43 69 48 04 56 62 00 81 49 31 73 55 79 14 29 93 71 40 67 53 88 30 03 49 13 36 65 52 70 95 23 04 …