从零开始实现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请 ...
随机推荐
- 使用 UniApp 实现小程序的微信登录
微信登录思路: 在main.js 中封装公共函数,用于判断用户是否登录 在main.js 中分定义全局变量,用于存储接口地址 如果没有登录.则跳转至登录页面 进入登录页面 通过 wx.login 获取 ...
- Hbase的基本架构以及对应的读写流程
一.HBase简介 1,定义: HBase 是一种分布式.可扩展.支持海量数据存储的 NoSQL 数据库. 2,HBase的架构图: 架构角色: 1)Master Master是所有Region Se ...
- linux pinmux 引脚多路复用驱动分析与使用
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明.本文链接:https://blog.csdn.net/code_style/article/de ...
- [转] 允许root通过ssh远程登录
点击阅读原文 在Ubuntu中允许root远程访问 如果使用如xshell等远程工具首次通过root连接Ubuntu会提示拒绝访问,并不是密码不正确,而是Ubuntu默认禁止以root远程连接. 我们 ...
- [转] VMware中的Ubuntu无法通过桥接方式上网
遇到的问题:主机可以上网(使用了代理),VMware设置为桥接方式连网.在主机中可以ping通虚拟机,在虚拟机中也可以ping通主机,可是在虚拟机中始终连接不了因特网. 感谢刘洋同学的博文,“在桥接模 ...
- 关键时刻,让你的iphone拒绝掉的所有来电
夜间被骚扰电话吵醒是会非常烦躁的,以下就是iphone的勿扰模式,配合刚出的夜间深夜模式非常的nice. 可以自定义设置时间段,每天智能切换. 也可以开启个人收藏的白名单,让家人有紧急事情也可以联系到 ...
- 在Centos7中,从主机 Windows 上无法远程访问 Linux 上rabbitmq的解决方法
当在 Linux 上配置好 Rabbitmq服务器后,如果从主机中无法访问到 Linux 中的Rabbitmq服务器时,需要做如下的检查: 1. Rabbitmq是否启动成功 在控制台输入: ps - ...
- 刷一遍《剑指Offer》,你还需要这些知识!(一刷)
因为时间紧和基础薄弱,一刷<剑指Offer>就变成了速看. 我按照: 1.看题目思考一会: 2.上网找找关于题目里不懂的知识点: 3.看评论和官方题解的解法,尽量看懂,并及时弄懂不懂的地方 ...
- Spring AOP学习笔记03:AOP的核心实现之获取增强器
上文讲了spring是如何开启AOP的,简单点说就是将AnnotationAwareAspectJAutoProxyCreator这个类注册到容器中,因为这个类最终实现了BeanPostProcess ...
- 0.1---selenium+java自动化测试进阶01---PageObject设计模式
一.PageObject设计模式 1.简介 PageObject设计模式,又称页面对象模式,是使用Selenium的广大同行最为公认的一种设计模式.在设计测试时,把元素和方法按照页面抽象出来,分离 ...