

从零开始实现multipart/form-data数据提交
source link: https://www.cnblogs.com/smark/p/13272357.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

在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, 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/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);
}
}
Recommend
-
12
PHP multipart/form-data 远程DOS漏洞 百度安全攻防实验室
-
42
This post is about handling file upload from server side, if you are also looking to send file in multipart with minimal memory from client side, please see this post. A: Nothing wrong with the code…
-
79
作者:Soroush Khanlou, 原文链接 ,原文日期:2018-11-14 译者: 郑一一
-
51
1. Node.js TypeScript #1. Modules, process arguments, basics of the File System 2.
-
51
无论是前端,还是后端开发,HTTP 接口的使用率实在是太高了。开发好了特定的 HTTP 接口,没有一个好的测试工具,怎么可以呢? 而 Postman 就是一款好用的爱不释手的测试工具,谁用谁说爽。 接口说明 ...
-
7
1. Form简介 Form
-
11
Multipart/form-data submit through javascript in Play Framework Reading Time: 2 minutesNormally when we submit any multi-p...
-
5
HTTP POST (multipart/form-data) on Android May 24, 2012 Since the c...
-
7
Mariajose Martinez October 24, 2022 6 minute re...
-
9
Golang multipart/form-data File Upload · GitHub Instantly share code, notes, and snippet...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK