在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);}}

从零开始实现multipart/form-data数据提交相关推荐

  1. 微信小程序 - 基础 - 003 - WEUI - 基本表单组件 - form - 页面数据提交和获取 - 01

    前言:form作为数据提交的重要控件,历来在前端设计中非常重要.微信给出了完整的form的例子.参考了一些微信设计的书籍,大多数都是copy 粘贴了例子的东西.... 其实微信的例子给的比较充分了: ...

  2. 1. 恼人的Multipart form data

    文章目录 1. 概述 2. 问题 3. 解决方案 3.1 解决方案一 3.2 解决方案二 3.3 解决方案三 4. 总结 1. 概述 我目前在公司负责开放平台项目,使用spring-cloud-gat ...

  3. EBS Form中数据提交方式(COMMIT)

    commit 对 f o rm 和数据库进行提交,如果 form 上面的数据和代码中的数据变动有冲突,最后以 FORM 上的为准. 适用情况:一般来在直接使用 DML 代码修改数据时,就使用 comm ...

  4. flux读取不到数据_WebFlux 中form data获取不到参数问题

    Spring WebFlux 中, request.queryParams 只能获取到 查询参数, 对于 form 提交的参数无法进行参数自动装载 处理方式有两种: 一. 自定义 ArgumentRe ...

  5. angularjs中$http模块发送post请求request payload转form data

    背景: ionic+ angularjs+ cordova 在开发一个证书照片删除的时候,后端提供了一个post接口,需要前端将数据转化成form data.而在angularjs中,如果直接用pos ...

  6. java multipart/form-data 提交_从零开始实现multipart/form-data数据提交

    在HTTP服务应用中进行数据提交一般都使用application/json,application/x-www-form-urlencoded和multipart/form-data这几种内容格式.这 ...

  7. python requests form data_python使用requests发送multipart/form-data请求数据

    def client_post_mutipart_formdata_requests(request_url,requestdict): #功能说明:发送以多部分表单数据格式(它要求post的消息体分 ...

  8. php form表单提交方式,form表单提交数据的几种方式

    一.submit提交 一般表单提交通过type=submit实现,input type="submit",浏览器显示为button按钮,通过点击这个按钮提交表单数据跳转到/url. ...

  9. form表单提交数据到后台的方式

    form表单提交方式 1.无刷新页面提交表单 表单可实现无刷新页面提交,无需页面跳转,如下,通过一个隐藏的iframe实现,form表单的target设置为iframe的name名称, form提交目 ...

最新文章

  1. MySQL 慢查询优化
  2. things to do in English debate: scenario
  3. Linux入门之进程管理(4)之进程与文件
  4. 浅谈 Linux 高负载的系统化分析
  5. roslaunch与param使用
  6. OpenCasCade在一个窗体中的两个picture控件中 分别显示
  7. 黑苹果升级驱动后WIFI不能启动的解决
  8. 大学生个人网页模板 简单网页制作作业成品 极简风格个人介绍HTML网页设计代码下载
  9. 联合概率分布与边缘分布
  10. 赋能房地产科技生态,“城越”加速器首期计划正式开启
  11. 不用爬虫,也能写一个聚合搜索引擎
  12. 蛋黄酥怎么做 蛋黄酥的做法
  13. 测试工程师如何提升自己
  14. 四元数船舶领域Quaternion ship domain
  15. PHP微信扫码关注登录实例
  16. 各种Java游戏引擎简介
  17. 20150317 实习之——余世维视频(上)
  18. Flex for .NET platform
  19. GetLastError 返回值意义
  20. python中rgb,python - 图像的RGB矩阵

热门文章

  1. javascript学习笔记 null和undefined
  2. [asp.net mvc 奇淫巧技] 04 - 你真的会用Action的模型绑定吗?
  3. Android推送通知指南(转)
  4. 如何使Flash在Internet Explorer的64位版本中工作
  5. 面向对象进阶(二)----------类的内置方法
  6. Android Studio3.0简介
  7. Ubuntu系统备份工具大全(官方整理推荐)
  8. Yet another nio framework for java
  9. MIT Scheme 使用 Edwin
  10. PHP中session与cookie的简单使用