从零开始实现multipart/form-data数据提交

在HTTP服务应用中进行数据提交一般都使用application/json,application/x-www-form-urlencodedmultipart/form-data这几种内容格式。这几种格式的处理复杂度处理起来和前面定义的先后顺序一样由易到难。不过现有工具都提供了完善的功能在提交这些数据的时候都比较方便了;不过要自己手动基础协议写起,那multipart/form-data的处理规范还是要相对复杂些。最近在写webapi管理和性能测试工具(https://github.com/IKende/WebBenchmark)时为了得到更可控的时间线和性能,在实现并没有用到任何应用组件都是从HTTP基础协议写起,在这时介绍一下如何在基础HTTP协议的基础上提交multipart/form-data数据.(如果你没有什么特别的需求还是不要这么干)

multipart/form-data

这种格式一般配合多数据类型提交使用,如常用的数据表单和文件结合。这种格式有着自己的处理规范和application/jsonapplication/x-www-form-urlencoded有着不同。application/json相对来说最简单整个数据流是json内容格式,而application/x-www-form-urlencoded则是以k-v的方式处理,只是对应的值要做Url编写。而multipart/form-data则用一个特别的分隔符来处理,这个分隔符分为开始分隔和结束分隔符。

分隔符定义

如果使用multipart/form-data提交数据,那必须在Content-Type的请求头后面添加; boundary=value这样一个描述,boundary的值即是每项数据之间的分隔符

mHeaderCached.Append("Content-Type: ").Append(mCases.ContentType);
if (multipartFormData)mHeaderCached.Append("; boundary=").Append(boundary);
mHeaderCached.Append("\r\n");

需要怎样定义boundary值?其实boundary的定义是没有特别的要求的,就是一个字符串完全看自己的喜好。但最终处理的时候是要有一个规范。

  • 开始分隔符-- boundary

  • 结束分隔符--boundary--

开始分隔符必须在每项数据之前写入,简单来说就是有多少项数据就有多少个开始分隔符了;结束分隔符只有一个,就是在提交内容的尾部添加,说明这个提交的内容在这里结束不需要再往下解释。大概格式如下:

-- boundary
数据项
-- boundary
数据项
-- boundary
数据项
-- boundary
数据项
--boundary--

数据项

multipart/form-data中的每项数据都分别有HeaderBody和整个HTTP上层协议差不多。

Content-Disposition: form-data; name="fname"\r\n
\r\n
value
\r\n

Content-Disposition是必须的,描述一下这数据的格式来源,在这里都是form-data;后面根据不同数据的情况有着不同的属性,每个属性用;分隔的K-V结构。代码的处理比较简单:

 mMemoryData.WriteLine($"Content-Disposition: form-data; name=\"{item.Name}\"");

接下来就是一个空换行然后再写入值,完整代码如下:

mMemoryData.WriteLine($"Content-Disposition: form-data; name=\"{item.Name}\"");
mMemoryData.WriteLine("");
mTextBodyCached.Clear();
item.GetTemplate().Execute(mTextBodyCached);
mMemoryData.Write(mTextBodyCached);
mMemoryData.WriteLine("");

提交文件

提交文件相对来说比值要处理多一些属性,主要包括内容类型,文件名等;其实写起来也不复杂

mMemoryData.WriteLine($"Content-Disposition: form-data; name=\"{item.Name}\"; filename=\"{item.FileName}\"");
mMemoryData.WriteLine($"Content-Type: {item.Type}");
mMemoryData.WriteLine("");
var itemBuffer = item.GetBuffer();
mMemoryData.Write(itemBuffer, 0, itemBuffer.Length);
mMemoryData.WriteLine("");

以上就是multipart/form-data普通值和文件提交时写的数据格式,下面看一下这个multipart/form-data的完整代码

for (int i = 0; i < mCases.FormBody.Count; i++)
{var item = mCases.FormBody[i];mMemoryData.Write("--");mMemoryData.WriteLine(boundary);if (item.Type == HttpDataType.Bytes){mMemoryData.WriteLine($"Content-Disposition: form-data; name=\"{item.Name}\"; filename=\"{item.FileName}\"");mMemoryData.WriteLine($"Content-Type: {item.Type}");mMemoryData.WriteLine("");var itemBuffer = item.GetBuffer();mMemoryData.Write(itemBuffer, 0, itemBuffer.Length);mMemoryData.WriteLine("");}else{mMemoryData.WriteLine($"Content-Disposition: form-data; name=\"{item.Name}\"");mMemoryData.WriteLine("");mTextBodyCached.Clear();item.GetTemplate().Execute(mTextBodyCached);mMemoryData.Write(mTextBodyCached);mMemoryData.WriteLine("");}
}
if (mCases.FormBody.Count > 0)
{mMemoryData.Write("--");mMemoryData.Write(boundary);mMemoryData.WriteLine("--");mMemoryData.Flush();
}

这样一个完整的multipart/form-data提交基础协议代码就处理完成;在webbenchmark的实现有还有application/jsonapplication/x-www-form-urlencoded的处理,相对于multipart/form-data来说这两个处理就更加简单了;下面包括:POST,GET,PUT,DELETE和三种数据格式提交的完整代码函(在BeetleX的pipestream帮助下这些协议的处理还是比较简单的)

public void Write(PipeStream stream)
{string boundary = null;bool multipartFormData = mCases.ContentType == "multipart/form-data";if (multipartFormData)boundary = "----Beetlex.io" + DateTime.Now.ToString("yyyyMMddHHmmss");byte[] bodyData = null;int bodyLength = 0;if (mHeaderCached == null)mHeaderCached = new StringBuilder();mHeaderCached.Clear();if (mMemoryData == null)mMemoryData = new PipeStream();if (mMemoryData.Length > 0)mMemoryData.ReadFree((int)mMemoryData.Length);if (mTextBodyCached == null)mTextBodyCached = new StringBuilder();mTextBodyCached.Clear();mHeaderCached.Append(mCases.Method).Append(" ");mUrlTemplate.Execute(mHeaderCached);for (int i = 0; i < mCases.QueryString.Count; i++){if (i == 0){if (mUrlHasParameter)mHeaderCached.Append("&");elsemHeaderCached.Append("?");}else{mHeaderCached.Append("&");}mHeaderCached.Append(mCases.QueryString[i].Name);mHeaderCached.Append("=");mCases.QueryString[i].GetTemplate().Execute(mHeaderCached, true);}mHeaderCached.Append(" ");mHeaderCached.Append(Protocol).Append("\r\n");foreach (var item in mCases.Header){mHeaderCached.Append(item.Name).Append(": ");item.GetTemplate().Execute(mHeaderCached);mHeaderCached.Append("\r\n");}mHeaderCached.Append("Content-Type: ").Append(mCases.ContentType);if (multipartFormData)mHeaderCached.Append("; boundary=").Append(boundary);mHeaderCached.Append("\r\n");if (multipartFormData){for (int i = 0; i < mCases.FormBody.Count; i++){var item = mCases.FormBody[i];mMemoryData.Write("--");mMemoryData.WriteLine(boundary);if (item.Type == HttpDataType.Bytes){mMemoryData.WriteLine($"Content-Disposition: form-data; name=\"{item.Name}\"; filename=\"{item.FileName}\"");mMemoryData.WriteLine($"Content-Type: {item.Type}");mMemoryData.WriteLine("");var itemBuffer = item.GetBuffer();mMemoryData.Write(itemBuffer, 0, itemBuffer.Length);mMemoryData.WriteLine("");}else{mMemoryData.WriteLine($"Content-Disposition: form-data; name=\"{item.Name}\"");mMemoryData.WriteLine("");mTextBodyCached.Clear();item.GetTemplate().Execute(mTextBodyCached);mMemoryData.Write(mTextBodyCached);mMemoryData.WriteLine("");}}if (mCases.FormBody.Count > 0){mMemoryData.Write("--");mMemoryData.Write(boundary);mMemoryData.WriteLine("--");mMemoryData.Flush();}}else if (mCases.ContentType == "application/json"){if (mJsonBodyTemplate != null){mJsonBodyTemplate.Execute(mTextBodyCached);}}else{for (int i = 0; i < mCases.FormBody.Count; i++){if (i > 0){mTextBodyCached.Append("&");}mTextBodyCached.Append(mCases.FormBody[i].Name).Append("=");mCases.FormBody[i].GetTemplate().Execute(mTextBodyCached, true);}}try{if (multipartFormData){bodyLength = (int)mMemoryData.Length;if (bodyLength > 0){bodyData = System.Buffers.ArrayPool<byte>.Shared.Rent(bodyLength);mMemoryData.Read(bodyData, 0, bodyLength);}}else{if (mTextBodyCached.Length > 0){char[] charbuffer = System.Buffers.ArrayPool<char>.Shared.Rent(mTextBodyCached.Length);try{mTextBodyCached.CopyTo(0, charbuffer, 0, mTextBodyCached.Length);bodyData = System.Buffers.ArrayPool<byte>.Shared.Rent(mTextBodyCached.Length * 6);bodyLength = Encoding.UTF8.GetBytes(charbuffer, 0, mTextBodyCached.Length, bodyData, 0);}finally{System.Buffers.ArrayPool<char>.Shared.Return(charbuffer);}}}mHeaderCached.Append("Content-Length: ").Append(bodyLength).Append("\r\n");mHeaderCached.Append("\r\n");stream.Write(mHeaderCached);if (bodyData != null){stream.Write(bodyData, 0, bodyLength);}}finally{if (bodyData != null)System.Buffers.ArrayPool<byte>.Shared.Return(bodyData);}}

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

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

相关文章

[mybatis]Configuration XML_mappers

mappers 将sql映射注册到全局配置中 mapper 注册一个sql映射 resource:引用类路径下的sql映射文件url:引用网络路径或者磁盘路径下的sql映射文件class:引用(注册)接口 1.有sql映射文件&#xff0c;映射文件名必须和接口同名&#xff0c;并且放在与接口同一目录下&#xff1b;…

Memento(备忘录)--对象行为型模式

Memento&#xff08;备忘录&#xff09;–对象行为型模式 一、意图 在不破坏封装性的前提下&#xff0c;捕获一个对象的内部状态&#xff0c;并在该对象之外保持这个状态。这样以后就可将该对象恢复到原先保存的状态。 二、动机 1.在软件构建过程中&#xff0c;某些对象的状态…

IBM、甲骨文、CNCF 就谷歌对 Istio 治理的处理提出抗议

近日来 Istio 商标转让、IBM 抗议谷歌违背承诺未将 Istio 捐献给 CNCF 的事情闹的沸沸扬扬。Google 宣布将 Istio 商标转让给 Open Usage Commons 组织IBM 声明对 Google 违背承诺未将 Istio 贡献给 CNCF 表示失望下面是据 TheRegister 的报道&#xff1a;谷歌创建了一个开放使…

[mybatis]全局配置文件标签顺序

propertiessettingstypeAliasestypeHandlersobjectFactoryobjectWrapperFactoryreflectorFactorypluginsenvironmentsdatabaseIdProvidermappers

Observer(观察者)--对象行为型模式

Observer&#xff08;观察者&#xff09;–对象行为型模式 一、意图 定义对象间的一种一对多的依赖关系&#xff0c;当一个对象的状态发生变化时&#xff0c;所有的依赖于它的对象都得到通知并被自动更新。 二、动机 1.在软件构建过程中&#xff0c;我们需要为某些对象建立一…

【今天下午活动】从 HelloWorld 到 AntDesign,Blazor 将 .NET 带到现代前端圈

Blazor 是一个 Web UI 框架&#xff0c;可通过 WebAssembly 在任意浏览器中运行 .Net 。Blazor 旨在简化快速的单页面 .Net 浏览器应用的构建过程&#xff0c;它虽然使用了诸如 CSS 和 HTML 之类的 Web 技术&#xff0c;但它使用 C&#xff03;语言和 Razor 语法代替 JavaScrip…

[mybatis]sqlSessionFactory.openSession()

第一个是不会自动提交的 第二个带参数的是会自动提交的

State(状态)--对象行为型模式

State&#xff08;状态&#xff09;–对象行为型模式 一、意图 允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。 二、动机 1.在软件构建过程中&#xff0c;某些对象的状态如果改变&#xff0c;其行为也会随之而发生变化&#xff0c;比如文档处于只…

推荐一个Asp.Net Core工具库

Masuit.Tools开源地址https://github.com/XiLife-OSPC/Masuit.Tools包含一些常用的操作类&#xff0c;大都是静态类&#xff0c;加密解密&#xff0c;反射操作&#xff0c;硬件信息&#xff0c;字符串扩展方法&#xff0c;日期时间扩展操作&#xff0c;大文件拷贝&#xff0c;图…

[mybatis]Mapper XML Files_CUD

The true power of MyBatis is in the Mapped Statements. This is where the magic happens. CUD mybatis运行CUD直接定义以下类型返回值 Integer,Long,Boolean 我们需要手动提交数据 sqlSessionFactory.openSession();—>手动提交sqlSessionFactory.openSession(true);—…

Strategy(策略)--对象行为型模式

Strategy&#xff08;策略&#xff09;–对象行为型模式 一、意图 定义一系列的算法&#xff0c;把它们一个个封装起来&#xff0c;并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。 二、动机 1.在软件构建过程中&#xff0c;某些对象使用的算法可能多种多…

12个Visual Studio调试效率技巧

在这篇文章中&#xff0c;我们假定读者了解VS基本的调试知识&#xff0c;如&#xff1a;F5 开始使用调试器运行程序F9 在当前行设置断点F10 运行到下一个断点处F5 从被调试的已停止程序恢复执行F11 步进到函数内&#xff08;如果当前程序指针指向一个函数&#xff09;F10 步过函…

Template Method(模板方法)--类行为型模式

Template Method&#xff08;模板方法&#xff09;–类行为型模式 一、意图 定义一个操作中的算法的骨架&#xff0c;而将一些步骤延迟到子类中。TemplateMethod使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。 二、动机 1.在软件构建过程中&#xff0c;对…

[mybatis]Mapper XML Files_获取自增主键的值

useGeneratedKeys “true”&#xff1b;使用自增主键获取主键值策略keyProperty&#xff1b;指定对应的主键属性&#xff0c;也就是mybatis获取主键值后&#xff0c;将这个值封装给javaBean的对应属性 <insert id"addEmp" useGeneratedKeys"true" keyP…

骚年快答 | 微服务架构中的BFF到底是啥?

【答疑解惑】| 作者 / Edison Zhou这是恰童鞋骚年的第263篇原创内容昨天的骚年快答《技术中台与业务中台都是啥玩意》一文中留下一个问题&#xff1a;BFF是啥&#xff1f;为啥在API网关和业务中台之间加入了一层BFF&#xff1f;考虑到在实际工作中&#xff0c;我的大部分同事都…

Visitor(访问者)--对象行为型模式

Visitor&#xff08;访问者&#xff09;–对象行为型模式 一、意图 表示一个作用于某个对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。 二、动机 1.在软件构建过程中&#xff0c;由于需求的改变&#xff0c;某些类层次结构中常…

[mybatis]映射文件_参数处理

参数处理 单个参数&#xff1a;mybatis不会做特殊处理 #{参数名}&#xff1a;取出参数值 多个参数&#xff1a;mybatis会做特殊处理 多个参数会被封装成一个map key&#xff1a;param1…paramN&#xff0c;或者参数的索引value&#xff1a;传入的参数值 #{}就是从map中获取指定…

骚年快答 | 技术中台与业务中台都是啥?

【答疑解惑】| 作者 / Edison Zhou这是恰童鞋骚年的第262篇原创内容最近有童鞋在我之前发布的《聊聊中台》一文中提问&#xff1a;技术中台是什么&#xff1f;和业务中台又有什么区别&#xff1f;考虑到在工作中&#xff0c;也有部分同事问过这个问题&#xff0c;我这里总结一下…

[mybatis]映射文件_参数处理_#和$取值区别

#{}&#xff1a;可以获取map中的值或者pojo对象属性的值${}&#xff1a;可以获取map中的值或者pojo对象属性的值 区别&#xff1a; #{}&#xff1a;是以预编译的形式&#xff0c;将参数设置到sql语句中&#xff0c;相当于原生jdbc的PreparedStatement&#xff1b;防止sql注入${…

平台or职位,你怎么选?

这里是Z哥的个人公众号每周五11&#xff1a;45 按时送达当然了&#xff0c;也会时不时加个餐&#xff5e;我的第「150」篇原创敬上大家好&#xff0c;我是Z哥。这周有位小伙伴和我聊到一个问题&#xff0c;选择工作的时候是平台重要还是职位重要&#xff1f;我想&#xff0c;很…