在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数据提交的更多相关文章

  1. Sending forms through JavaScript[form提交 form data]

    https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Sending_forms_through_JavaScript As in the ...

  2. html5 file upload and form data by ajax

    html5 file upload and form data by ajax 最近接了一个小活,在短时间内实现一个活动报名页面,其中遇到了文件上传. 我预期的效果是一次ajax post请求,然后在 ...

  3. Form表单提交数据的几种方式

    一.submit提交 在form标签中添加Action(提交的地址)和method(post),且有一个submit按钮(<input type='submit'>)就可以进行数据的提交, ...

  4. 关于AJAX与form表单提交数据的格式

    一 form表单传输文件的格式: 只有三种: multipart/form-data 一般用于传输文件,图片文件或者其他的. 那么其中我们默认的是application/x-www-form-urle ...

  5. 表单提交数据格式form data

    前言: 最近遇到的最多的问题就是表单提交数据格式问题了. 常见的三种表单提交数据格式,分别举例说明:(项目是vue的框架) 1.application/x-www-form-urlencoded 提交 ...

  6. 使用form表单提交请求如何获取后台返回的数据?

    问题描述 一般的form表单提交是单向的:只能给服务器发送数据,但是无法获取服务器返回的数据,也就是无法读取HTTP应答包. 想要真正的半双工通讯一般需要使用Ajax, 但是Ajax对文件传输也很麻烦 ...

  7. ligerui_实际项目_003:form中添加数据,表格(grid)里面显示,最后将表格(grid)里的数据提交到servlet

    实现效果: "Form"中填写数据,向本页"Grid"中添加数据,转换成Json数据提交,计算总和,Grid文本框可编辑,排序 图片效果: 总结: //disp ...

  8. Ajax模拟Form表单提交,含多种数据上传

    ---恢复内容开始--- Ajax提交表单.使用FormData提交表单数据和上传的文件(这里的后台使用C#获取,你可以使用Java一样获取) 有时候前台的数据提交到后台,不想使用form表单上传,希 ...

  9. springMVC中对HTTP请求form data和request payload两种数据发送块的后台接收方式

    最近在做项目中发现,前台提交数据时,如果通过form表单提交和ajax发送json时,springMVC后台接收不能都通过@ModelAttribute方式处理,经过一番查找后,ajax发送json请 ...

随机推荐

  1. centos7下docker的安装教程

    Centos7下docker安装教程以及踩过的那些坑 推荐在Centos下安装docker,在windows下安装docker可能会遇到很多的问题,而且docker官方推荐使用linux环境来使用do ...

  2. 纯 HTML/CSS 高仿 Win10 加载动画

    自己做的超高仿Win10加载动画(应该是全网最像的 HTML 实现了),自己想用就拿去用吧 转圈加载 在线演示 HTML: <div class="loading"> ...

  3. 如何控制excel单元格录入相同内容的次数?

    我们知道如果要用Excel制作让人规范填写的模板,数据有效性,Excel2013版本及以上叫数据验证是必学的技能,正好这个案例可以讲讲数据有效性的一种“高级”设置方法. 加入我们需要实现下面的要求,价 ...

  4. Python爬虫小白入门(七)爬取豆瓣音乐top250

      抓取目标: 豆瓣音乐top250的歌名.作者(专辑).评分和歌曲链接 使用工具: requests + lxml + xpath. 我认为这种工具组合是最适合初学者的,requests比pytho ...

  5. VScode快捷键集锦(持续更新)

    本文的快捷键是根据VScode for mac来的,Windows和Linux用户可能需要些许改动. 注释:cmd + /  注释当前选中的行,或者注释掉选中的行,再按一次反注释.还可以在按cmd + ...

  6. 几款一元单片机对比:CMS8S5880、STM8S003、N76E003

    大概17年开始,STM8S003的价格被贸易商炒货,变得很不稳定,一度上涨到2~3元,因为市场需求大增,小家电.无线充和一些简单功能的产品,本人就有在空气净化器.433M触摸开关.数据收发模块.红外控 ...

  7. scanf中的%[^\n]%*c格式

    scanf中的%[^\n]%*c格式 (2011-02-19 16:12:38) 转载▼ 标签:  控制字符 空白字符 字符串 变量 整数 it 分类: C语言编程 文章转载自http://blog. ...

  8. 利用bat文件打开浏览器指定网页,提示 windows找不到chrome.exe。请确定文件名是否正确,再试一次 的解决经历

      1.在网上找到一些bat命令,原来电脑可以正常使用,效果就是执行后打开谷歌浏览器并全屏展示某网页,但是在昨天换个一个电脑后发现不能用了,提示以下截图的错误 2.找了半天发现问题所在: 把chrom ...

  9. WeChair——团队展示

    这个作业属于哪个课程 软件工程 (福州大学至诚学院 - 计算机工程系) 团队名称 WeChair 这个作业要求在哪里 团队作业第一次 这个作业的目标 团队合作,项目开发 作业正文 如下 其他参考文献 ...

  10. Vuex怎么用(1)

    1. vuex是什么 github站点: https://github.com/vuejs/vuex在线文档: https://vuex.vuejs.org/zh-cn/简单来说: 对应用中组件的状态 ...