在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, , 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/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("&");
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. vue对象数组数据变化,页面不渲染

    很多时候,我们习惯于这样操作数组和对象: data() { // data数据 return { arr: [1,2,3], obj:{ a: 1, b: 2 } }; }, // 数据更新 数组视图 ...

  2. 【转载】有人出天价买他的一个文案标题,今天10min教会你……

    目录 1. 套路 1:新闻社论 2. 套路 2:好友对话 3. 套路 3:实用锦囊 4. 套路 4:惊喜优惠 5. 套路 5:意外故事 本文由 简悦 SimpRead 转码, 原文地址 https:/ ...

  3. (八)MySQL事务、视图、变量、存储过程、函数、流程控制结构

    补充:增删查改语句在数据库中基本通用,但这篇博客的内容基本是MySQL区别于其它数据库管理系统的知识,也要认真学习. 一.事务 1.含义:在MySQL中,可以通过创建事务来解决一些问题. 2.语法: ...

  4. iOS-键盘弹出或隐藏时调整输入框的位置

    要达到自动调整的目标需要监听 keyboardWillShowNotification 跟 keyboardWillHideNotification, 同时需要实现点击其它地方时,通知隐藏键盘的事件 ...

  5. pip安装mysql-connector

    pip install mysql-connector --index-url https://pypi.douban.com/simple

  6. vscode 常用变量

    ${workspaceFolder} the path of the workspace folder that contains the tasks.json file ${workspaceRoo ...

  7. python中列表,元组,字符串 互相转换

    #-*-coding:utf-8-*- #1.字典dict = {'name': 'Zara', 'age': 7, 'class': 'First'} #字典转为字符串,返回:<type 's ...

  8. 前端笔记:div只显示两行内容,多出内容以...显示

    代码: text-overflow: -o-ellipsis-lastline;overflow: hidden;text-overflow: ellipsis;display: -webkit-bo ...

  9. Java的前生今世

    Java作为一门编程语言,自诞生以来已经流行了20多年,在学习它之前,我们有必要先了解一下它的历史,了解它是如何一步步发展到今天这个样子. 孕育 上世纪90年代,硬件领域出现了单片式计算机系统,比如电 ...

  10. Celery浅谈

    一.Celery 核心模块 1. Brokers brokers 中文意思为中间人,在这里就是指任务队列本身,接收生产者发来的消息即Task,将任务存入队列.任务的消费者是Worker,Brokers ...