1. 程式人生 > 其它 >post表單資料格式完全解析multipart/form-data(C#實現)

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;

        }