下面是“C#记一次http协议multipart/form-data的boundary问题”的完整攻略。
1. 问题背景
在使用 C# 发送 HTTP 请求时,如果请求体采用 multipart/form-data
格式,则需要在请求头和请求体中添加对应 "Content-Type"
和 "Boundary"
。其中 "Boundary"
是分割每个 form-data
的标识字符。
常见的发送 multipart/form-data
请求体的方式为利用 HttpWebRequest
类发送 POST 请求。
例如,我们需要向服务器发送以下数据:
{
"username": "huangzy",
"avatar": 二进制图片数据
}
我们可以使用以下 C# 代码来实现:
var boundary = "------------------------" + DateTime.Now.Ticks.ToString("x");
var formData = "--" + boundary + "\r\n" +
"Content-Disposition: form-data; name=\"username\"" + "\r\n\r\n" +
"huangzy" + "\r\n" +
"--" + boundary + "\r\n" +
"Content-Disposition: form-data; name=\"avatar\"; filename=\"avatar.jpg\"" + "\r\n" +
"Content-Type: image/jpeg" + "\r\n\r\n" +
// 二进制图片数据 + "\r\n" +
"--" + boundary + "--" + "\r\n";
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://example.com/api/upload");
request.Method = "POST";
request.ContentType = "multipart/form-data; boundary=" + boundary;
using (Stream stream = request.GetRequestStream())
{
byte[] formDataBytes = Encoding.UTF8.GetBytes(formData);
stream.Write(formDataBytes, 0, formDataBytes.Length);
// 写入二进制数据流...
}
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
在以上代码中,我们在请求头中添加了 "Content-Type": "multipart/form-data; boundary=xxx"
,其中 "boundary"
是分割各个 form-data
数据的标识字符串。在请求体中,我们按照 "boundary"
的格式拼凑了 form-data
数据。
2. 问题分析
在上述代码中,我们拼凑 "boundary"
的方式如下:
var boundary = "------------------------" + DateTime.Now.Ticks.ToString("x");
这种方式只会在当前请求中生成一个随机的 "boundary"
,如果我们需要循环发送多个请求,则会出现问题。
例如以下场景:我们需要向服务器发送 3 个文件,每个文件大小不超过 1MB。这种情况下,我们不能将所有的文件都存储在内存中,而需要每传输一部分数据,就立即释放内存。因此我们需要分别对每个文件发送一个 HTTP 请求。这时,我们的代码可能是这样的:
foreach (var file in files)
{
var boundary = "------------------------" + DateTime.Now.Ticks.ToString("x");
var formData = "--" + boundary + "\r\n" +
"Content-Disposition: form-data; name=\"file\"" + "\r\n" +
"Content-Type: application/octet-stream" + "\r\n\r\n";
formData += // 添加文件二进制数据...;
formData += "\r\n--" + boundary + "--\r\n";
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://example.com/api/upload");
request.Method = "POST";
request.ContentType = "multipart/form-data; boundary=" + boundary;
using (Stream stream = request.GetRequestStream())
{
byte[] formDataBytes = Encoding.UTF8.GetBytes(formData);
stream.Write(formDataBytes, 0, formDataBytes.Length);
// 写入文件二进制数据...
}
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
}
在以上代码的循环中,每次生成的 "boundary"
均不同。因此,发送的请求中 "boundary"
也都不同。如果服务器端无法正确解析出请求体中的 "boundary"
,就会返回错误的响应。
3. 解决方案
为了解决上述问题,我们需要确定请求中的 "boundary"
值。
对于没有重复文件名的数据发送,我们可以使用以下方式:
var boundary = "----------------------------" + DateTime.Now.Ticks.ToString("x");
该 "boundary"
的值与上一种方式类似,只是去掉了 "-"
的个数,防止出现 "boundary"
与请求体内容重叠的情况。
对于重复文件名的情况,我们需要特别处理。由于不同的文件大小不同,如果直接使用同一个 "boundary"
,将可能返回错误的响应。
为了解决该问题,我们需要使用文件名 + 文件大小作为 "boundary"
的值:
var byteArray = File.ReadAllBytes(file); // 获取文件二进制数据
var stream = new MemoryStream(byteArray);
var boundary = "----------------------------" + DateTime.Now.Ticks.ToString("x");
var formData = "--" + boundary + "\r\n" +
"Content-Disposition: form-data; name=\"file\"; filename=\"" + Path.GetFileName(file) + "\"\r\n" +
"Content-Type: application/octet-stream\r\n\r\n";
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://example.com/api/upload");
request.Method = "POST";
request.ContentType = "multipart/form-data; boundary=" + boundary;
using (var reqStream = request.GetRequestStream())
{
var formDataBytes = Encoding.UTF8.GetBytes(formData);
reqStream.Write(formDataBytes, 0, formDataBytes.Length);
var buffer = new byte[4096];
int bytesRead = 0;
while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) != 0)
{
reqStream.Write(buffer, 0, bytesRead);
}
var end = Encoding.ASCII.GetBytes("\r\n--" + boundary + "--\r\n");
reqStream.Write(end, 0, end.Length);
}
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
对于以上代码,我们将文件名 + 文件大小作为 "boundary"
的值,避免了同一个 "boundary"
重复使用的情况。
以上就是针对 C# 中发送 multipart/form-data
的 "boundary"
问题的完整攻略。
4. 示例
下面我提供两个示例,分别是:
- 发送不同文件的
multipart/form-data
请求 - 发送同一文件的
multipart/form-data
请求
示例1:发送不同文件的 multipart/form-data
请求
以下是示例代码:
// 我们需要发送的文件列表
var files = new List<string>
{
"/path/to/file1.txt",
"/path/to/file2.jpg",
"/path/to/file3.png"
};
// boundary 字符串
var boundary = "----------------------------" + DateTime.Now.Ticks.ToString("x");
// 循环发送每个文件
foreach (var file in files)
{
// 初始化 HttpWebRequest 对象
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://example.com/api/upload");
request.Method = "POST";
request.ContentType = "multipart/form-data; boundary=" + boundary;
// 读取文件数据
byte[] byteArray = File.ReadAllBytes(file);
// 构造请求体
string header = "--" + boundary + Environment.NewLine +
"Content-Disposition: form-data; name=\"file\"; filename=\"" + Path.GetFileName(file) + "\"" + Environment.NewLine +
"Content-Type: application/octet-stream" + Environment.NewLine +
Environment.NewLine;
string footer = Environment.NewLine + "--" + boundary + "--" + Environment.NewLine;
byte[] headerBytes = Encoding.UTF8.GetBytes(header);
byte[] footerBytes = Encoding.UTF8.GetBytes(footer);
using (Stream stream = request.GetRequestStream())
{
stream.Write(headerBytes, 0, headerBytes.Length);
stream.Write(byteArray, 0, byteArray.Length);
stream.Write(footerBytes, 0, footerBytes.Length);
}
// 发送请求并接收响应
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
// TODO 处理响应
}
}
在以上代码中,我们循环发送每个文件,每个请求独立生成 "boundary"
并构造请求体。
示例2:发送同一文件的 multipart/form-data
请求
以下是示例代码:
// 文件路径
var file = "/path/to/same/file.txt";
// 获取文件数据
byte[] byteArray = File.ReadAllBytes(file);
// boundary 字符串,由文件名 + 文件大小组成
var boundary = "----------------------------" + file.Length + "_" + DateTime.Now.Ticks.ToString("x");
// 初始化 HttpWebRequest 对象
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://example.com/api/upload");
request.Method = "POST";
request.ContentType = "multipart/form-data; boundary=" + boundary;
// 构造请求体
string header = "--" + boundary + Environment.NewLine +
"Content-Disposition: form-data; name=\"file\"; filename=\"" + Path.GetFileName(file) + "\"" + Environment.NewLine +
"Content-Type: application/octet-stream" + Environment.NewLine +
Environment.NewLine;
string footer = Environment.NewLine + "--" + boundary + "--" + Environment.NewLine;
byte[] headerBytes = Encoding.UTF8.GetBytes(header);
byte[] footerBytes = Encoding.UTF8.GetBytes(footer);
using (Stream stream = request.GetRequestStream())
{
stream.Write(headerBytes, 0, headerBytes.Length);
stream.Write(byteArray, 0, byteArray.Length);
stream.Write(footerBytes, 0, footerBytes.Length);
}
// 发送请求并接收响应
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
// TODO 处理响应
}
在以上代码中,我们使用文件名 + 文件大小作为 "boundary"
值,并循环发送请求。在这个过程中,"boundary"
的值不变,避免了发送请求时值不一致的问题。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:C#记一次http协议multipart/form-data的boundary问题 - Python技术站