筆者有一個項目中用到了上傳zip並解壓的功能。開始覺得很簡單,因爲之前曾經做過之類的上傳文件的功能,所以並不爲意,於是使用copy大法。正如你所料,如果一切很正常的能運行的話就不會有這篇筆記了。
整個系統跑起來以後,在本地開發環境中測試,順利執行。測試環境中,順利執行。隨着項目的推進,上線。這個功能在前期本身是不重要的,不過當你沒有服務器權限的時候,有一個可以隨意上傳文件的功能還是很不錯的,再也不用寫郵件,等待,等待,等待,而是可以很快看到修改結果,這樣想想還是令人小激動的。 so? 出來什麼問題呢?
在一套模板製作完畢並上傳的時候,問題來了,這是jquery 中彈出的錯誤
鬼能看的懂。於是本地調整了接口,指向到本地的api,讓api項目進入調試狀態,再次上傳文件。在費了n多調整步驟之後,抓到了錯誤:
遠程服務器返回錯誤:(400)錯誤的請求
這是什麼鬼?? 從來沒見過呀!怎麼沒有錯誤提示呢??偶買噶!當時筆者的內芯是奔潰的。
立馬百度,還真有好多人遇到這個問題,看了n多方案後還是跟我的情況不像。不行,那就分析吧。
筆者的程序中有這個一個函數
1 /// <summary> 2 /// 發起httpPost 請求,可以上傳文件 3 /// </summary> 4 /// <param name="url">請求的地址</param> 5 /// <param name="files">文件</param> 6 /// <param name="input">表單數據</param> 7 /// <param name="endoding">編碼</param> 8 /// <returns></returns> 9 public static string PostResponse(string url, UpLoadFile[] files, Dictionary<string, string> input, Encoding endoding) 10 { 11 12 string boundary = "----------------------------" + DateTime.Now.Ticks.ToString("x"); 13 HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); 14 request.ContentType = "multipart/form-data; boundary=" + boundary; 15 request.Method = "POST"; 16 request.KeepAlive = true; 17 //request.Credentials = CredentialCache.DefaultCredentials; 18 request.Expect = ""; 19 20 MemoryStream stream = new MemoryStream(); 21 22 23 byte[] line = Encoding.ASCII.GetBytes("--" + boundary + "\r\n"); 24 byte[] enterER = Encoding.ASCII.GetBytes("\r\n"); 25 ////提交文件 26 if (files != null) 27 { 28 string fformat = "Content-Disposition:form-data; name=\"{0}\"; filename=\"{1}\"\r\nContent-Type:{2}\r\n\r\n"; 29 foreach (UpLoadFile file in files) 30 { 31 32 stream.Write(line, 0, line.Length); //項目分隔符 33 string s = string.Format(fformat, file.Name, file.FileName, file.Content_Type); 34 byte[] data = Encoding.UTF8.GetBytes(s); 35 stream.Write(data, 0, data.Length); 36 stream.Write(file.Data, 0, file.Data.Length); 37 stream.Write(enterER, 0, enterER.Length); //添加\r\n 38 } 39 } 40 41 42 //提交文本字段 43 if (input != null) 44 { 45 string format = "--" + boundary + "\r\nContent-Disposition:form-data;name=\"{0}\"\r\n\r\n{1}\r\n"; //自帶項目分隔符 46 foreach (string key in input.Keys) 47 { 48 string s = string.Format(format, key, input[key]); 49 byte[] data = Encoding.UTF8.GetBytes(s); 50 stream.Write(data, 0, data.Length); 51 } 52 53 } 54 55 byte[] foot_data = Encoding.UTF8.GetBytes("--" + boundary + "--\r\n"); //項目最後的分隔符字符串需要帶上-- 56 stream.Write(foot_data, 0, foot_data.Length); 57 58 59 60 request.ContentLength = stream.Length; 61 Stream requestStream = request.GetRequestStream(); //寫入請求數據 62 stream.Position = 0L; 63 stream.CopyTo(requestStream); // 64 stream.Close(); 65 66 requestStream.Close(); 67 68 69 70 try 71 { 72 73 74 HttpWebResponse response; 75 try 76 { 77 response = (HttpWebResponse)request.GetResponse(); 78 79 try 80 { 81 using (var responseStream = response.GetResponseStream()) 82 using (var mstream = new MemoryStream()) 83 { 84 responseStream.CopyTo(mstream); 85 string message = endoding.GetString(mstream.ToArray()); 86 return message; 87 } 88 } 89 catch (Exception ex) 90 { 91 throw ex; 92 } 93 } 94 catch (WebException ex) 95 { 96 //response = (HttpWebResponse)ex.Response; 97 98 99 //if (response.StatusCode == HttpStatusCode.BadRequest) 100 //{ 101 // using (Stream data = response.GetResponseStream()) 102 // { 103 // using (StreamReader reader = new StreamReader(data)) 104 // { 105 // string text = reader.ReadToEnd(); 106 // Console.WriteLine(text); 107 // } 108 // } 109 //} 110 111 throw ex; 112 } 113 114 115 } 116 catch (Exception ex) 117 { 118 throw ex; 119 } 120 }
當然這個函數是正確的,那個錯誤的已經被修改掉了。
通過調試,傳遞進來的數據都是正確的那麼問題肯定是出在http數據包的拼接上了。
出於習慣,準備把整段代碼換掉,實現功能後再分析錯誤所在,於是百度一下 HttpWebRequest 上傳文件的代碼,由於我要實現的是多文件上傳,那些單文件上傳的例子都被我pass掉,實驗了幾個網上的例子後覺得還是不行。於是自己提取了數據,使用Advanced REST client(chrome插件) 工具模擬post,分析它post時候發送是數據包格式
經過自己對比,發現筆者的程序中在\n\rContent-Type之前有一個分號(;)在上文中代碼的第41行處,奔潰在數據包內容部分最後的分隔符 結尾不是以 --\r\n結尾的 在上文代碼中54行,於是修改了這兩處及對程序做了細微的調整。並再次上傳文件得到api返回結果。
至此這給bug總算搞定,不過lz寫了這多多字就是爲了記錄這次修改的勝利麼!no no no,不是那樣,我是要記錄一下這http post數據包的格式。
筆者隨後查閱資料得到:
這個一個請求報文的格式,請求行和請求頭部都可以通過 HttpWebRequest 對象的一些屬性來添加,具體有哪些自己百度一下吧,筆者也不是十分清楚,這裏就不給出資料了
請求數據需要自己 使用字符串拼接,下面給出筆者在這次事件中得到的經驗
-------------------------------------------------------------------------------------------------------
請求數據每一個行需要以括號內的(--分隔符\r\n ) 進行分割,注意--是必須帶的,例如:
"--" + boundary+"\r\n" 其中boundary 是自定義的分隔符
表單數據最後一項之後的分隔符是以 --\r\n結尾的,如下示例:
------WebKitFormBoundarydhy7IYZyMgmp2cLv--
實際上他是 "--" + boundary+"--\r\n" ,可以參考上文中代碼 54行
---------------------------------------------------------------------------------------------
讀者如果覺得我講的比較迷糊可以參考下下面這篇博文進行對比學習
http://blog.csdn.net/five3/article/details/7181521
聽說Post傳輸數據有好幾種形式哦,後續繼續分析,歡迎拍磚