從HTTP的multipart/form-data分析看C#後臺 HttpWebRequest檔案上傳
在web請求中ContentType一直沒有過多關注,也知道ContentType設定為application/x-www-from-urlencoded或multipart/form-data 後和介面層後臺獲取引數值有重要關係。但都沒有稍深入研究,這次研究了一番,記錄於此!
首先為何常見的web前端檔案上傳使用的是multipart/form-data 而非x-www-from-urlencoded.
使用webuploader等上傳外掛或postman等工具發起一個檔案上傳的請求,再使用Fiddler或Chrome Network等工具檢視網路請求。看下面截圖
檔案上傳時Content-Type為 multipart/form-data,
對照C#後臺程式碼的寫法類似下面這樣
// 邊界符
var boundary = "---------------" + DateTime.Now.Ticks.ToString("x");
webRequest.ContentType = "multipart/form-data; boundary=" + boundary;
再看請求體的內容
上面的引數內容格式非常有規律:分析大概特性是:
1:一行“------WebKitFormBoundary{$xxx}”;
所以會看到C#的程式碼寫法如下:(此處為何是ASCII 呢,因為http協議以ASCII碼傳輸?)
// 開始邊界符 var beginBoundary = Encoding.ASCII.GetBytes("--" + boundary + "\r\n"); using (var stream = new MemoryStream()) { // 寫入開始邊界符 stream.Write(beginBoundary, 0, beginBoundary.Length); }
2: 一行固定內容:Content-Disposition:form-data; name="xxx", 然後空一行,再寫入內容值;
以上傳檔案為例後臺C#程式碼對應如下
// 組裝檔案頭資料體 到記憶體流中 string fileHeaderTemplate = string.Format("Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"\r\nContent-Type: application/octet-stream\r\n\r\n", parameter.FileNameKey, parameter.FileNameValue); byte[] fileHeaderBytes = parameter.Encoding.GetBytes(fileHeaderTemplate); memoryStream.Write(fileHeaderBytes, 0, fileHeaderBytes.Length);
其實上面的格式來源Http協議規範,此處轉載他人blog 內容:
Rfc1867中可查
1. 上傳檔案請求頭:
Content-type:multipart/form-data; boundary=---+boundary(注1)
--------------------+boundary(注2)
Content-Disposition: form-data; name=file;filename=test.txt;
Content-Type: text/plain;
------------------+boundary--(注3)
a) 注1一般用系統時間(一串數字)來做為boundary值
b) 注1和注2前面的------不可省,它是做為分隔符存在的
c) 注2必須單獨一行
d) 兩個content-type,第一個告訴客戶端我要用表單上傳檔案,第二個表示上傳的檔案型別
如果不知道檔案型別的話,可以設為application/octet-stream,以二進位制流的形式上傳下載
e) --{boundary} http協議的Form的分隔符,表示結束的話在其後面加—,如注3
另外在每一段資訊描述後要跟一個\r\n再跟檔案資料,檔案資料後面也要跟一個\r\n
f) 分隔符的標誌:---------7d是IE特有的標誌,Mozila為-------------71.
g) 多檔案上傳必須用multipart/mixed
https://blog.csdn.net/sinat_38364990/article/details/70867357
參考原創文章:https://blog.csdn.net/five3/article/details/7181521
再看Http Content-Type=multipart/form-data 的好處是 檔案的 表單引數可以通過 ---{boundary}很多的區分和識別,而www-from-urlencoded 是key value的組合形式,無法區分檔案內容和表單值,故上傳檔案需要使用mutipart/form-data.
引用如下英文stackoverflow上的回答來輔助瞭解:
https://stackoverflow.com/questions/3508338/what-is-the-boundary-in-multipart-form-data
If you want to send the following data to the web server:
name = John
age = 12
usingapplication/x-www-form-urlencoded
would be like this:
name=John&age=12
As you can see, the server knows that parameters are separated by an ampersand&
. If&
is required for a parameter value then it must be encoded.
So how does the server know where a parameter value starts and ends when it receives an HTTP request usingmultipart/form-data
?
Using theboundary, similar to&
.
For example:
--XXX
Content-Disposition: form-data; name="name"
John
--XXX
Content-Disposition: form-data; name="age"
12
--XXX--
In that case, the boundary value isXXX
. You specify it in theContent-Type
header so that the server knowshow to splitthe data it receives.
So you need to:
Use a value that won't appear in the HTTP data sent to the server.
Be consistent and use the same value everywhere in the request message.
最後看一個C# 後臺使用HttpWebRequest上傳檔案附件的例子:
https://www.cnblogs.com/GodX/p/5604944.html
/// <summary>
/// Http上傳檔案類 - HttpWebRequest封裝
/// </summary>
public
class
HttpUploadClient
{
/// <summary>
/// 上傳執行 方法
/// </summary>
/// <param name="parameter">上傳檔案請求引數</param>
public
static
string
Execute(UploadParameterType parameter)
{
using
(MemoryStream memoryStream =
new
MemoryStream())
{
// 1.分界線
string
boundary =
string
.Format(
"----{0}"
, DateTime.Now.Ticks.ToString(
"x"
)),
// 分界線可以自定義引數
beginBoundary =
string
.Format(
"--{0}\r\n"
, boundary),
endBoundary =
string
.Format(
"\r\n--{0}--\r\n"
, boundary);
byte
[] beginBoundaryBytes = parameter.Encoding.GetBytes(beginBoundary),
endBoundaryBytes = parameter.Encoding.GetBytes(endBoundary);
// 2.組裝開始分界線資料體 到記憶體流中
memoryStream.Write(beginBoundaryBytes, 0, beginBoundaryBytes.Length);
// 3.組裝 上傳檔案附加攜帶的引數 到記憶體流中
if
(parameter.PostParameters !=
null
&& parameter.PostParameters.Count > 0)
{
foreach
(KeyValuePair<
string
,
string
> keyValuePair
in
parameter.PostParameters)
{
string
parameterHeaderTemplate =
string
.Format(
"Content-Disposition: form-data; name=\"{0}\"\r\n\r\n{1}\r\n{2}"
, keyValuePair.Key, keyValuePair.Value, beginBoundary);
byte
[] parameterHeaderBytes