post表單資料格式完全解析multipart/form-data(C#實現)
post表單資料格式完全解析multipart/form-data
引數說明 |
內容 |
boundary |
boundary一個字串,用以分隔不同的引數; string boundary = Guid.NewGuid().ToString(); 會生成如下字串:9470b619-f08f-436e-a6b2-98fcb02b695b 也可以自定義一個比較複雜的字串作為分隔符,例如@adsadsadsads123456789@ request.ContentType = "multipart/form-data;charset=utf-8;boundary=" + boundary; --boundary開始分隔符,也就是boundary之前加兩個短劃線,用以標記每一條資料的起始位置; --boundary--結束分隔符,也就是boundary之前和之後各加兩個短劃線,用以標記所有資料的結束位置; |
\r\n |
換行符\r\n,用以標記分隔符和引數宣告之間、各引數宣告之間、引數宣告和引數值之間、引數值和下一條資料之間的分隔位置; --boundary和資料宣告之間必須有且只有1個換行符; 各條資料宣告之間有且只有1個換行符; 資料宣告和資料值之間不少於2個換行符; 資料值和下一條資料之間至少1個換行符; |
其他引數 |
CContent-Type:text/plain;charset=utf-8表示資料內容的格式為普通文字,編碼格式為utf-8; Content-Disposition:form-data表示內容的處置方式,表示將內容作為formdata格式進行處理; name=\"sInputF\"表示傳遞引數的名稱,或者叫鍵值、或者欄位名稱; 所有資料,無論是文字資料還是檔案流資料,最終都以byte[]的格式進行傳輸; |
示例 |
--boundary //第一條資料 資料宣告1 資料宣告2 資料 --boundary//第二條資料 資料宣告1 資料宣告2 資料 ... --boundary--//資料結束 |
結構詳解
引數 |
專案 |
內容 |
開始 |
換行符 |
第一個開始分隔符前可以沒有換行符\r\n |
引數1 sInputF 檔案引數 |
開始分隔符 |
"--" + boundary |
換行符 |
有且只有1個換行符\r\n |
|
資料宣告1: |
Content-Disposition:form-data;name=\"sInputF\";filename=\"{Path.GetFileName(mDocPath)}\" |
|
換行符 |
有且只有1個換行符\r\n |
|
資料宣告2: |
Content-Type:application/octet-stream |
|
換行符 |
宣告和資料值之間,至少要有2個換行符\r\n\r\n |
|
資料值 |
byte[] docFileBytes = ReadFileBytes(mDocPath); |
|
換行符 |
至少1個換行符\r\n |
|
引數2 sSealF 檔案引數 |
開始分隔符 |
"--" + boundary |
換行符 |
有且只有1個換行符\r\n |
|
資料宣告1: |
Content-Disposition:form-data;name=\"sSealF\";filename=\"{Path.GetFileName(mSealPath)}\" |
|
換行符 |
有且只有1個換行符\r\n |
|
資料宣告2: |
Content-Type:application/octet-stream |
|
換行符 |
宣告和資料值之間,至少要有2個換行符\r\n\r\n |
|
資料值 |
byte[] sealFileBytes = ReadFileBytes(mSealPath); |
|
換行符 |
至少1個換行符\r\n |
|
引數3 sPageNum 文字引數 |
開始分隔符 |
"--" + boundary |
換行符 |
有且只有1個換行符\r\n |
|
資料宣告1: |
CContent-Type:text/plain;charset=utf-8 |
|
換行符 |
有且只有1個換行符\r\n |
|
資料宣告2: |
Content-Disposition:form-data;name=\"sPageNum\" |
|
換行符 |
宣告和資料值之間,至少要有2個換行符\r\n\r\n |
|
資料值 |
1 |
|
換行符 |
至少1個換行符\r\n |
|
引數4 fSealW 文字引數 |
開始分隔符 |
"--" + boundary |
換行符 |
有且只有1個換行符\r\n |
|
資料宣告1: |
CContent-Type:text/plain;charset=utf-8 |
|
換行符 |
有且只有1個換行符\r\n |
|
資料宣告2: |
Content-Disposition:form-data;name=\"fSealW\" |
|
換行符 |
宣告和資料值之間,至少要有2個換行符\r\n\r\n |
|
資料值 |
100 |
|
換行符 |
至少1個換行符\r\n |
|
引數5 fSealH 文字引數 |
開始分隔符 |
"--" + boundary |
換行符 |
有且只有1個換行符\r\n |
|
資料宣告1: |
CContent-Type:text/plain;charset=utf-8 |
|
換行符 |
有且只有1個換行符\r\n |
|
資料宣告2: |
Content-Disposition:form-data;name=\"fSealH\" |
|
換行符 |
宣告和資料值之間,至少要有2個換行符\r\n\r\n |
|
資料值 |
1 |
|
換行符 |
至少1個換行符\r\n |
|
引數6 fSealPosX 文字引數 |
開始分隔符 |
"--" + boundary |
換行符 |
有且只有1個換行符\r\n |
|
資料宣告1: |
CContent-Type:text/plain;charset=utf-8 |
|
換行符 |
有且只有1個換行符\r\n |
|
資料宣告2: |
Content-Disposition:form-data;name=\"fSealPosX\" |
|
換行符 |
宣告和資料值之間,至少要有2個換行符\r\n\r\n |
|
資料值 |
1 |
|
換行符 |
至少1個換行符\r\n |
|
引數7 fSealPosY 文字引數 |
開始分隔符 |
"--" + boundary |
換行符 |
有且只有1個換行符\r\n |
|
資料宣告1: |
CContent-Type:text/plain;charset=utf-8 |
|
換行符 |
有且只有1個換行符\r\n |
|
資料宣告2: |
Content-Disposition:form-data;name=\"fSealPosY\" |
|
換行符 |
宣告和資料值之間,至少要有2個換行符\r\n\r\n |
|
資料值 |
1 |
|
換行符 |
至少1個換行符\r\n |
|
結束 |
結束分隔符 |
"--" + boundary "--",結束分隔符之後可以沒有換行符 |
原始碼1
void Request(string url) { HttpWebRequest request = WebRequest.CreateHttp(url);//建立request物件,不能通過建構函式建立 request.Method = "POST";//設定請求方法 GET、HEAD、POST、PUT、DELETE、TRACE 或 OPTIONS string boundary = Guid.NewGuid().ToString(); // 隨機分隔線 //設定資料型別為multipart/form-data,多部分/資料表單型別,該型別用於傳遞檔案資料的情形,當然也可以傳遞文字引數 request.ContentType = "multipart/form-data;charset=utf-8;boundary=" + boundary; //構建資料,無論文字資料還是檔案資料,最終都要以位元組流的形式進行傳送 //構建資料:word文件檔案 // "--" + boundary表示一條資料的開頭 //第一個"\r\n"表示開始分隔符和資料宣告之間的分隔符,二者之間有且只能有1個換行符,如果沒有或者超過1個,都將導致資料解析失敗 //Content-Disposition:form-data;name=\"sInputF\";filename=\"{Path.GetFileName(mDocPath)}\"表示一條資料宣告 //Content-Disposition:form-data表示資料的處置方式為表單資料 //name=\"sInputF\"表示該引數名稱、欄位名、鍵 //filename=\"{Path.GetFileName(mDocPath)}\"上傳檔案,指明檔名稱 //第二個\r\n表示第一條資料宣告和第二條資料宣告之間的分隔符,二者之間有且只能有1個換行符,如果沒有或者超過1個,都將導致資料解析失敗 //Content - Type:application / octet - stream表示資料型別為位元組流 //最後的\r\n\r\n表示資料宣告和資料內容之間的分隔符,二者只有至少有2個換行符,如果換行符超過2個,可以正常解析,如果少於2個,將導致資料解析失敗 string docStr = "--" + boundary + "\r\n" + $"Content-Disposition:form-data;name=\"sInputF\";filename=\"{Path.GetFileName(mDocPath)}\"\r\nContent-Type:application/octet-stream\r\n\r\n\r\n\r\n\r\n"; byte[] docFileBytes = ReadFileBytes(mDocPath);//從檔案中讀取位元組資料 //構建資料:圖片檔案 string sealStr = "--" + boundary + "\r\n" + $"Content-Disposition:form-data;name=\"sSealF\";filename=\"{Path.GetFileName(mSealPath)}\"\r\nContent-Type:application/octet-stream\r\n\r\n"; byte[] sealFileBytes = ReadFileBytes(mSealPath);//從檔案中讀取位元組資料 //構建資料:普通文字引數 //每一條資料都已條目分隔符開始,然後後面按照格式拼接相關資料 //Content-Type: text/plain; charset=utf-8表示內容的格式是普通文字,編碼格式為charset=utf-8 //Content-Disposition:form-data表示資料處理方式,作為表單資料進行處理 //name表示引數名、欄位名、或者鍵 //第一個"\r\n",表示開始分隔符和資料宣告之間的分隔符,二者之間有且只能有1個換行符,如果沒有或者超過1個,都將導致資料解析失敗 //第二個\r\n表示第一條資料宣告和第二條資料宣告之間的分隔符,二者之間有且只能有1個換行符,如果沒有或者超過1個,都將導致資料解析失敗 //最\r\n\r\n表示資料宣告和資料內容之間的分隔符,二者只有至少有2個換行符,如果換行符超過2個,可以正常解析,如果少於2個,將導致資料解析失敗 //最後的\r\n表示本條資料內容和下一條資料之間,至少有1個換行符,如果超過1個,可以正常解析,如果沒有該換行符,將導致資料解析失敗 string pageStr = "--" + boundary + "\r\n" + $"Content-Type:text/plain;charset=utf-8\r\nContent-Disposition:form-data;name=\"sPageNum\"\r\n\r\n\r\n\r\n\r\n{1}\r\n\r\n\r\n\r\n\r\n\r\n"; string wStr = "--" + boundary + "\r\n" + $"Content-Type:text/plain;charset=utf-8\r\nContent-Disposition:form-data;name=\"fSealW\"\r\n\r\n{1}\r\n"; string hStr = "--" + boundary + "\r\n" + $"Content-Type:text/plain;charset=utf-8\r\nContent-Disposition:form-data;name=\"fSealH\"\r\n\r\n{1}\r\n"; string xStr = "--" + boundary + "\r\n" + $"Content-Type:text/plain;charset=utf-8\r\nContent-Disposition:form-data;name=\"fSealPosX\"\r\n\r\n{1}\r\n"; string yStr = "--" + boundary + "\r\n" + $"Content-Type:text/plain;charset=utf-8\r\nContent-Disposition:form-data;name=\"fSealPosY\"\r\n\r\n{1}\r\n"; //將所有資料轉換為位元組陣列,併合並在一起 List<byte> byteList = new List<byte>(); //第一個檔案 byteList.AddRange(Encoding.UTF8.GetBytes(docStr));//新增檔案資料頭 byteList.AddRange(docFileBytes);//新增檔案資料 byteList.AddRange(Encoding.UTF8.GetBytes("\r\n"));//本條資料內容和下一條資料之間,至少有1個換行符,如果沒有該換行符,將導致資料解析失敗 //第二個檔案 byteList.AddRange(Encoding.UTF8.GetBytes(sealStr));//新增檔案資料頭 byteList.AddRange(sealFileBytes);//新增檔案資料 byteList.AddRange(Encoding.UTF8.GetBytes("\r\n"));//本條資料內容和下一條資料之間,至少有1個換行符,如果沒有該換行符,將導致資料解析失敗 //所有的文字引數 byteList.AddRange(Encoding.UTF8.GetBytes(pageStr));//新增文字引數資料 byteList.AddRange(Encoding.UTF8.GetBytes(wStr));//新增文字引數資料 byteList.AddRange(Encoding.UTF8.GetBytes(hStr));//新增文字引數資料 byteList.AddRange(Encoding.UTF8.GetBytes(xStr));//新增文字引數資料 byteList.AddRange(Encoding.UTF8.GetBytes(yStr));//新增文字引數資料 //所有資料的最後,要新增結尾分隔符 byteList.AddRange(Encoding.UTF8.GetBytes("--" + boundary + "--")); //設定寫入資料的長度,並將資料寫入 request.ContentLength = byteList.Count;//設定資料長度,byte為單位 Stream postStream = request.GetRequestStream();//獲取HttpWebRequest的資料流物件 postStream.Write(byteList.ToArray(), 0, byteList.Count);//將資料寫入,開始位置為0,寫入數量為byteList.Count postStream.Close();//寫完後,關閉流物件 WebResponse response = request.GetResponse();//傳送請求 獲取迴應 //獲取迴應的文字內容 Stream stream = response.GetResponseStream();//獲取響應的流 StreamReader reader = new StreamReader(stream); string content = reader.ReadToEnd();//讀取響應流 reader.Close();//關閉流 //構建列印資訊 string showStr = docStr + mDocPath + "\n檔案位元組數=" + docFileBytes.Length + "\n" + sealStr + mSealPath + "\n檔案位元組數=" + sealFileBytes.Length + "\n" + pageStr + wStr + hStr + xStr + yStr + "\r\n" + "--" + boundary + "--"; //顯示相關資訊 richTextBox1.Text = "返回資訊:\n" + content + "\n\n請求頭資訊:\n" + request.Headers.ToString() + "傳送的資料:\n" + showStr; } |
原始碼2
void Request2() { //建立request物件,不能通過建構函式建立 HttpWebRequest request = WebRequest.CreateHttp(mPostUrl); //設定請求方法 GET、HEAD、POST、PUT、DELETE、TRACE 或 OPTIONS request.Method = "POST"; //獲取隨機分隔符 string boundary = Guid.NewGuid().ToString(); //設定內容格式 request.ContentType = "multipart/form-data;charset=utf-8;boundary=" + boundary; //通過引數字典,獲取所有位元組資料 List<byte> byteList = GetAllPostData(mFiles, mParams, boundary); //設定內容長度 request.ContentLength = byteList.Count; //獲取請求的流物件 並寫入位元組資料 Stream postStream = request.GetRequestStream(); postStream.Write(byteList.ToArray(), 0, byteList.Count); postStream.Close(); //獲取響應 WebResponse response = request.GetResponse();//傳送請求 獲取迴應 //獲取迴應的文字內容 Stream stream = response.GetResponseStream(); StreamReader reader = new StreamReader(stream); string content = reader.ReadToEnd(); reader.Close(); //顯示相關資訊 richTextBox1.Text = "返回資訊:\n" + content; } //普通引數 Dictionary<string, string> mParams = new Dictionary<string, string> { { "sPageNum","1"}, { "fSealW","100"}, { "fSealH","100"}, { "fSealPosX","100"}, { "fSealPosY","100"}, }; //檔案引數 Dictionary<string, string> mFiles = new Dictionary<string, string> { { "sInputF",mDocPath}, { "sSealF",mSealPath}, }; //獲取檔案引數的位元組資料 List<byte> GetFilePostData(string field, string path, string boundary) { List<byte> res = new List<byte>(); string head = "--" + boundary + "\r\n" + $"Content-Disposition:form-data;name=\"{field}\";filename=\"{Path.GetFileName(path)}\"\r\nContent-Type:application/octet-stream\r\n\r\n"; res.AddRange(Encoding.UTF8.GetBytes(head)); res.AddRange(ReadFileBytes(path)); res.AddRange(Encoding.UTF8.GetBytes("\r\n")); return res; } //獲取普通引數的位元組資料 List<byte> GetParamPostData(string field, string value, string boundary) { List<byte> res = new List<byte>(); string head = "--" + boundary + "\r\n" + $"Content-Type:text/plain;charset=utf-8\r\nContent-Disposition:form-data;name=\"{field}\"\r\n\r\n{value}\r\n"; res.AddRange(Encoding.UTF8.GetBytes(head)); return res; } //獲取所有位元組資料 List<byte> GetAllPostData(Dictionary<string, string> fileDic, Dictionary<string, string> paramDic, string boundary) { List<byte> res = new List<byte>(); foreach (var item in fileDic) { List<byte> temp = GetFilePostData(item.Key, item.Value, boundary); res.AddRange(temp); } foreach (var item in paramDic) { List<byte> temp = GetFilePostData(item.Key, item.Value, boundary); res.AddRange(temp); } res.AddRange(Encoding.UTF8.GetBytes("\r\n--" + boundary + "--\r\n")); return res; } //讀取檔案中的所有位元組 byte[] ReadFileBytes(string path) { if (!File.Exists(path)) return new byte[0]; FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read); int iLenStream = (int)fs.Length; byte[] bArr = new byte[fs.Length]; fs.Read(bArr, 0, bArr.Length); fs.Close(); return bArr; } |