1. 程式人生 > >從HTTP的multipart/form-data分析看C#後臺 HttpWebRequest檔案上傳

從HTTP的multipart/form-data分析看C#後臺 HttpWebRequest檔案上傳

在web請求中ContentType一直沒有過多關注,也知道ContentType設定為application/x-www-from-urlencodedmultipart/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)    分隔符的標誌:---------7dIE特有的標誌,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-urlencodedwould 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-Typeheader 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<stringstring> 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