从零开始实现multipart/form-data数据提交
在HTTP服务应用中进行数据提交一般都使用application/json,application/x-www-form-urlencoded和multipart/form-data这几种内容格式。这几种格式的处理复杂度处理起来和前面定义的先后顺序一样由易到难。不过现有工具都提供了完善的功能在提交这些数据的时候都比较方便了;不过要自己手动基础协议写起,那multipart/form-data的处理规范还是要相对复杂些。最近在写webapi管理和性能测试工具(https://github.com/IKende/WebBenchmark)时为了得到更可控的时间线和性能,在实现并没有用到任何应用组件都是从HTTP基础协议写起,在这时介绍一下如何在基础HTTP协议的基础上提交multipart/form-data数据.(如果你没有什么特别的需求还是不要这么干)
multipart/form-data
这种格式一般配合多数据类型提交使用,如常用的数据表单和文件结合。这种格式有着自己的处理规范和application/json和application/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中的每项数据都分别有Header和Body和整个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, , itemBuffer.Length);
mMemoryData.WriteLine("");
以上就是multipart/form-data普通值和文件提交时写的数据格式,下面看一下这个multipart/form-data的完整代码
for (int i = ; 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, , 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 > )
{
mMemoryData.Write("--");
mMemoryData.Write(boundary);
mMemoryData.WriteLine("--");
mMemoryData.Flush();
}
这样一个完整的multipart/form-data提交基础协议代码就处理完成;在webbenchmark的实现有还有application/json和application/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("&");
else
mHeaderCached.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);
} }
从零开始实现multipart/form-data数据提交的更多相关文章
- Sending forms through JavaScript[form提交 form data]
https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Sending_forms_through_JavaScript As in the ...
- html5 file upload and form data by ajax
html5 file upload and form data by ajax 最近接了一个小活,在短时间内实现一个活动报名页面,其中遇到了文件上传. 我预期的效果是一次ajax post请求,然后在 ...
- Form表单提交数据的几种方式
一.submit提交 在form标签中添加Action(提交的地址)和method(post),且有一个submit按钮(<input type='submit'>)就可以进行数据的提交, ...
- 关于AJAX与form表单提交数据的格式
一 form表单传输文件的格式: 只有三种: multipart/form-data 一般用于传输文件,图片文件或者其他的. 那么其中我们默认的是application/x-www-form-urle ...
- 表单提交数据格式form data
前言: 最近遇到的最多的问题就是表单提交数据格式问题了. 常见的三种表单提交数据格式,分别举例说明:(项目是vue的框架) 1.application/x-www-form-urlencoded 提交 ...
- 使用form表单提交请求如何获取后台返回的数据?
问题描述 一般的form表单提交是单向的:只能给服务器发送数据,但是无法获取服务器返回的数据,也就是无法读取HTTP应答包. 想要真正的半双工通讯一般需要使用Ajax, 但是Ajax对文件传输也很麻烦 ...
- ligerui_实际项目_003:form中添加数据,表格(grid)里面显示,最后将表格(grid)里的数据提交到servlet
实现效果: "Form"中填写数据,向本页"Grid"中添加数据,转换成Json数据提交,计算总和,Grid文本框可编辑,排序 图片效果: 总结: //disp ...
- Ajax模拟Form表单提交,含多种数据上传
---恢复内容开始--- Ajax提交表单.使用FormData提交表单数据和上传的文件(这里的后台使用C#获取,你可以使用Java一样获取) 有时候前台的数据提交到后台,不想使用form表单上传,希 ...
- springMVC中对HTTP请求form data和request payload两种数据发送块的后台接收方式
最近在做项目中发现,前台提交数据时,如果通过form表单提交和ajax发送json时,springMVC后台接收不能都通过@ModelAttribute方式处理,经过一番查找后,ajax发送json请 ...
随机推荐
- 提高编译速度! 第一次运行需要注释掉,不然会报错,因为需要编译SO库文件 !
// 提高编译速度! 第一次运行需要注释掉,不然会报错,因为需要编译SO库文件 ! tasks.whenTaskAdded { task -> if (task.name.contains(&q ...
- OSI七层模型及各层作用
物理层:建立.维护.断开物理连接 数据链路层:该层的作用包括了物理地址寻址,数据的成帧,流量控制,数据的检错,重发等.该层控制网络层与物理层之间的通信,解决的是所传输数据的准确性的问题.为了保证传输, ...
- 本地配置gitee
一 下载工具 Git-2.62.0-64-bit.exe 以上工具版本号不需要一样,安装完前两个后重新启动系统,再安装第3个. 二 码云网站注册 https://gitee.com/ 使用邮箱注册 注 ...
- 痞子衡嵌入式:链接函数到8字节对齐地址或可进一步提升i.MXRT内核执行性能
大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家分享的是i.MXRT上进一步提升代码执行性能的经验. 今天跟大家聊的这个话题还是跟痞子衡最近这段时间参与的一个基于i.MXRT1170的大项目有 ...
- Firefox中input元素,不能重新获取焦点函数focus()
js校验输入框的函数: function is_number(feild) { var strRegExp = /^\d+(\.\d{1,2})?$/; if (!strRegExp.test(fei ...
- Unit1-窝窝初体验
全文共3179字,推荐阅读时间10~15分钟. 文章共分四个部分: 作业分析 评测相关 重构策略 初体验感受 作业分析 第一次作业 第一次作业要求我们实现一个简单的幂函数求导工具,没有乘积和复合的情况 ...
- 线上排查Class、Jar加载问题的一般方法
问题背景 本问题源于<ojdbc6中OraclePreparedStatement的ArrayIndexOutOfBoundsException异常BUG-6396242>这篇博文中最后思 ...
- git添加所有新文件
git add -A 提交所有变化 git add -u 提交被修改(modified)和被删除(deleted)文件,不包括新文件(new) git add . 提交新文件(new)和被修改(mod ...
- Android使用OkHttp实现登录注册功能
客户端 在客户端,这里将登录和注册放在了同一个界面,在账号和密码两个EditText中输入内容后,按下LOGIN按钮,进行登录:按下REGISTER按钮,进行注册. 在写代码之前,先添加OkHttp的 ...
- Shiro反序列化复现
Shiro反序列化复现 ——————环境准备—————— 目标靶机:10.11.10.108 //docker环境 攻击机ip:无所谓 vpsip:192.168.14.222 //和靶机ip可通 1 ...