从零开始实现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请 ...
随机推荐
- 3、react-props/state
1.react中属性props和状态state 属性--静态得,所以在初始化得时候使用得是static进行初始化得,正常情况下属性不改 状态--动态得,它得值是可以发生改变得,react中的组件更新( ...
- 04.Java基础语法
一.Java源程序结构与编程规范 一个完整的Java源程序应该包含下列部分 package语句,至多一句,必须放在源程序第一句 import语句,没有或者若干句,必须放在所有类定义前 public c ...
- MySQL数据库离线包安装与注册
本文主要介绍了MySQL数据库的离线安装和将MySQL服务注册为Windows应用服务的主要步骤. 1.下在安装程序包 MySQL Community Server 5.6.15 官方下载地址http ...
- flutter-web利用dart js 库发起http request
初学flutter,初学前端,尝试在dart中直接使用HttpClient时,直接报出Platform not supported,查资料发现他还不支持浏览器. 通过查阅资料发现可以借助axios 与 ...
- swift - TextView和TextField之return隐藏回收键盘
一.点击界面空白处即可收起键盘,空白处不能有其他控件的响应事件. //点击空白处关闭键盘 override func touchesEnded(_ touches: Set<UITouch> ...
- postman切换环境
原文链接:https://www.cnblogs.com/nicole-zhang/p/11498384.html 通常会有多个测试环境,针对同一个接口来说,可能只是域名有变化,此时可以添加postm ...
- 浅淡i.MX8M Mini处理器的效能以及平台对比
i.MX 8M Mini是恩智浦首款嵌入式多核应用处理器,定位在任何通用工业和物联网的应用,是一款针对边缘计算应用的芯片,也是恩智普i.MX系列中第一个加了机器学习核的产品线.这颗芯片采用先进的14L ...
- 网页中为什么常用AT替换@(repost from https://zhidao.baidu.com/question/122291.html)
经常在个人主页上看到别人的邮箱地址中@被AT符号替代,很是迷惑,这样替代有什么好处呢?还是说html原有的原因使界面中不能出现@,查阅资料后解答如下: 写成AT [at],是为了防止被一些邮件扫描器搜 ...
- 最新 iOS 框架整体梳理(三)
这一篇得把介绍框架这个系列终结了,不能超过三篇了,不然太长了..... 还是老规矩,前面两篇的机票在下方: 最新 iOS 框架整体梳理(一) 最新 iOS 框架整体梳理(二) Part - 3 ...
- 利用VS自带发布功能实现web项目快速部署
你还在使用最原始的方法部署服务器吗?还在把项目文件全部复制然后黏贴到服务器上?这种方法太low而且又慢又不安全(存在源码泄露等安全性问题),如果你是这样,那你自己肯定也为此烦恼不已. 下面我所要讲到的 ...