在使用 HttpWebRequest Post数据时候返回 400错误
笔者有一个项目中用到了上传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传输数据有好几种形式哦,后续继续分析,欢迎拍砖