当前位置:   article > 正文

ASP.NET WebApi 上传文件时异常 Failed to execute send on XMLHttpRequest 的一个处理方法_networkerror: failed to execute 'send' on 'xmlhttp

networkerror: failed to execute 'send' on 'xmlhttprequest': failed to load

笔者最近写了一个通过ASP.NET MVC4 WebApi、jQuery、ajax和FormData上传文件的系统(见基于ASP.NET MVC4、WebApi、jQuery和FormData的多文件上传方法),在自己的笔记本上测试一切正常,但发布到客户服务器(云服务器,Windows Server 2012 操作系统,有很强防火墙保护)时,部分文件上传正常,但多数文件上传抛出如下异常信息(被jquery的ajax 的 error 捕获):NetworkError: Failed to execute 'send' on 'XMLHttpRequest': Failed to load http://xxx/Api/FilesApi/Upload。这里,Upload是路由api、控制器FilesApi的Post方法。

网查了许多方法,一般说是跨域调用引起的异常,但使用介绍的相关方法都没有解决该问题。笔者估计是防火墙检查了通过http传入的文件内容以及文件名,或者是浏览器有很强的内容过滤功能,发现它认为有威胁的关键字符串,就拒绝调用相关Api方法。为此,笔者想到了通过html5引入的FileReader对象(FormData也是html5引入),在客户端使用该对象的异步方法readAsURLData获取文件内容的base64加密文本(注意,FileReader.readAsURLData()方法使用了UTF-8编码做base64加密),然后通过FormData发送该文本,在服务器WebApi中解密该文本,然后保存文件。同样,使用了一个base64加密的js插件,加密上传的文件名。

如下是Home控制器对应的客户端脚本(仅上传两个文件)Index.cshtml:

  1. @model CSUST.Files.TDirItems
  2. @{
  3. Layout = null;
  4. }
  5. <!DOCTYPE html>
  6. <html>
  7. <head>
  8. <title>文件上传</title>
  9. <script type="text/javascript" src='@Url.Action("jquery-1.12.4.min.js", "scripts")'></script>
  10. <script type="text/javascript" src='@Url.Action("jquery.base64.min.js", "scripts")'></script>
  11. <script type="text/javascript">
  12. var fileData = [];
  13. $(document).ready(function ()
  14. {
  15. $("#tbFileName1").on("change", function(){
  16. GetFile(this);
  17. });
  18. $("#tbFileName2").on("change", function () {
  19. GetFile(this);
  20. });
  21. });
  22. function GetFile(fileObj)
  23. {
  24. fileData[fileObj.id] = null;
  25. var fileName = $(fileObj).val();
  26. if (fileName == null || fileName == "") // 文件名为空
  27. {
  28. return;
  29. }
  30. var files = $(fileObj).get(0).files;
  31. if (files[0].size > 4 * 1024 * 1024)
  32. {
  33. alert("单个文件不能大于4M。");
  34. $(fileObj).val("");
  35. return;
  36. }
  37. var reader = new FileReader();
  38. reader.readAsDataURL(files[0]);
  39. reader.onload = function ()
  40. {
  41. var base64 = reader.result;
  42. var n = base64.indexOf("base64,");
  43. fileData[fileObj.id] = base64.substr(n + 7);
  44. }
  45. }
  46. function Clear()
  47. {
  48. $("#tbFileName1").val("");
  49. $("#tbFileName2").val("");
  50. }
  51. function Upload()
  52. {
  53. var f1 = $("#tbFileName1").val();
  54. var f2 = $("#tbFileName2").val();
  55. if ((f1 == null || f1 == "") && (f2 == null || f2 == ""))
  56. {
  57. alert("至少要上传一个文件。");
  58. return;
  59. }
  60. if (f1 != "" && fileData["tbFileName1"] == null)
  61. {
  62. alert("尚未读取文件1,稍后!");
  63. return;
  64. }
  65. if (f2 != "" && fileData["tbFileName2"] == null)
  66. {
  67. alert("尚未读取文件2,稍后!");
  68. return;
  69. }
  70. if(f1 == f2)
  71. {
  72. alert("不能上传相同文件。");
  73. return;
  74. }
  75. var n1 = $("#tbTicket").val();
  76. var n2 = $("#ckAllowNewFiles").is(":checked");
  77. var n3 = $("#cbDirNameKeys").val();
  78. if (n1 == "")
  79. {
  80. alert("必须输入验证口令。");
  81. return;
  82. }
  83. if (n3.indexOf("\\") == 0)
  84. {
  85. alert("不能选择\\注释格式的文件夹项。");
  86. return;
  87. }
  88. var formData = new FormData();
  89. formData.append("VerifyTicket", n1);
  90. formData.append("AllowNewFiles", n2);
  91. formData.append("DirNameKey", n3);
  92. if (f1 != "")
  93. {
  94. formData.append("FileName1", $.base64('encode', f1));
  95. formData.append("FileContent1", fileData["tbFileName1"]);
  96. }
  97. if (f2 != "")
  98. {
  99. formData.append("FileName2", $.base64('encode', f2));
  100. formData.append("FileContent2", fileData["tbFileName2"]);
  101. }
  102. SendFiles(formData);
  103. }
  104. function SendFiles(formData)
  105. {
  106. $.ajax({
  107. type: "post",
  108. url: '@Url.Action("Upload", "Api/FilesApi")',
  109. async: false,
  110. data: formData,
  111. contentType: false,
  112. processData: false,
  113. success: function (data, status)
  114. {
  115. if (status != "success")
  116. {
  117. alert("上传文件失败: " + status);
  118. return;
  119. }
  120. if (data == null)
  121. {
  122. alert("上传文件失败, 没有返回结果。");
  123. return;
  124. }
  125. if (data.IsFailed == true)
  126. {
  127. alert(data.ErrorMessage);
  128. return;
  129. }
  130. alert(data.Note);
  131. $("#tbFileName1").val("");
  132. $("#tbFileName2").val("");
  133. },
  134. error: function (xhr, status, err)
  135. {
  136. alert("上传文件异常: " + status + "," + err);
  137. }
  138. });
  139. }
  140. </script>
  141. </head>
  142. <body>
  143. <form id="Form1">
  144. <div align="center">
  145. <h2><label>@Model.WebSiteTitle</label></h2>
  146. <table style="width: 1050px;" border="1">
  147. <tr style="height: 32px">
  148. <td rowspan="2" style="width: 120px;text-align:center">
  149. 文件名
  150. </td>
  151. <td colspan="2" align="left">
  152. <input ID="tbFileName1" name="tbFileName1" type="file" style="width: 96%;"/>
  153. </td>
  154. </tr>
  155. <tr style="height: 32px">
  156. <td colspan="2" align="left">
  157. <input ID="tbFileName2" name="tbFileName2" type="file" style="width: 96%;"/>
  158. </td>
  159. </tr>
  160. <tr style="height: 42px">
  161. <td style="text-align: center">到文件夹</td>
  162. <td style="width: 650px; text-align: left;">
  163. <select ID="cbDirNameKeys" style="width: 650px;">
  164. @foreach(CSUST.Files.TDirItem item in Model.DirItems)
  165. {
  166. <option>@item.DirNameKey</option>
  167. }
  168. </select>
  169. </td>
  170. <td style="width: 280px; text-align:left;">
  171. <input type="checkbox" ID="ckAllowNewFiles" />
  172. <label for="ckAllowNewFiles">新增cshtml.css.js.jpg等文件</label>
  173. </td>
  174. </tr>
  175. <tr style="height: 42px;">
  176. <td style="height: 42px; text-align: center;">校验口令</td>
  177. <td style="height: 42px; text-align: left;">
  178. <input type="password" id="tbTicket" style="width: 645px;" />
  179. </td>
  180. <td style="height: 42px; text-align:center;">
  181. <input type="button" ID="bnUpload" value="上传文件" οnclick="Upload()" style="width: 96px; height: 32px;"/>   
  182. <input type="button" ID="bnClearFile" value="清空文件" οnclick="Clear()" style="width: 96px; height: 32px;" />
  183. </td>
  184. </tr>
  185. </table>
  186. </div>
  187. </form>
  188. </body>
  189. </html>
如下是服务器端ASP.NET MVC4的WebApi对应的POST方法Upload:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Web;
  4. using System.Web.Http;
  5. namespace CSUST.Files
  6. {
  7. public class FilesApiController : ApiController
  8. {
  9. [HttpPost]
  10. public CSUST.Web.TWebApiResult Upload()
  11. {
  12. try
  13. {
  14. var httpRequest = HttpContext.Current.Request;
  15. var dirNameKey = httpRequest.Form["DirNameKey"];
  16. var allowNewFiles = httpRequest.Form["AllowNewFiles"];
  17. var verifyTicket = httpRequest.Form["VerifyTicket"];
  18. if (dirNameKey.StartsWith(TDirItem.NoteChars) == true)
  19. {
  20. return new Web.TWebApiResult("不能选择" + TDirItem.NoteChars + "注释格式的文件夹项。");
  21. }
  22. TDirItems dirItems = new TDirItems();
  23. if (dirItems.VerifyTicket != verifyTicket)
  24. {
  25. return new Web.TWebApiResult("验证口令错误。");
  26. }
  27. List<string> fileNames = new List<string>();
  28. List<string> fileContents = new List<string>();
  29. if (string.IsNullOrWhiteSpace(httpRequest.Form["FileName1"]) == false)
  30. {
  31. string fileName1 = this.GetFileNameByBase64(httpRequest.Form["FileName1"]);
  32. fileNames.Add(fileName1);
  33. fileContents.Add(httpRequest.Form["FileContent1"]);
  34. }
  35. if (string.IsNullOrWhiteSpace(httpRequest.Form["FileName2"]) == false)
  36. {
  37. string fileName2 = this.GetFileNameByBase64(httpRequest.Form["FileName2"]);
  38. fileNames.Add(fileName2);
  39. fileContents.Add(httpRequest.Form["FileContent2"]);
  40. }
  41. CSUST.Text.TStringItemsBuilder sb = new Text.TStringItemsBuilder(Environment.NewLine);
  42. foreach (var fn in fileNames)
  43. {
  44. if (dirItems.IsAllowedFileName(dirNameKey, fn) == false)
  45. {
  46. sb.AppendItem(fn + "不能保存到指定的文件夹中。");
  47. }
  48. var saveFileName = dirItems.GetSavedFileName(dirNameKey, fn);
  49. if (System.IO.File.Exists(saveFileName) == false && allowNewFiles.ToUpper() != "TRUE")
  50. {
  51. sb.AppendItem(fn + "新文件时必须勾选新增复选框。");
  52. }
  53. }
  54. if (sb.Length > 0)
  55. {
  56. return new Web.TWebApiResult(sb.ToString());
  57. }
  58. sb.Clear();
  59. sb.AppendItem("保存文件成功:");
  60. for (int k = 0; k < fileNames.Count; k++)
  61. {
  62. var saveFileName = dirItems.GetSavedFileName(dirNameKey, fileNames[k]);
  63. byte[] fb = Convert.FromBase64String(fileContents[k]);
  64. using (System.IO.FileStream fs = new System.IO.FileStream(saveFileName, System.IO.FileMode.Create, System.IO.FileAccess.Write))
  65. {
  66. fs.Write(fb, 0, fb.Length);
  67. fs.Flush();
  68. sb.AppendItem(saveFileName);
  69. }
  70. }
  71. CSUST.Web.TWebApiResult r = new Web.TWebApiResult() { Note = sb.ToString() };
  72. return r;
  73. }
  74. catch (Exception err)
  75. {
  76. return new CSUST.Web.TWebApiResult(err, true);
  77. }
  78. }
  79. private string GetFileNameByBase64(string base64FileName)
  80. {
  81. byte[] b = Convert.FromBase64String(base64FileName);
  82. string fileName = System.Text.Encoding.UTF8.GetString(b); // 前端使用了base64加密,防止文本串被防火墙拒绝
  83. return System.IO.Path.GetFileName(fileName);
  84. }
  85. }
  86. }

经过上述技术处理后提交一般文件正常,但在上传Global.asax文件时,仍然抛出上述异常。测试时把该文件改名为@@Global.asax则可正常上传。显然,浏览器或防火墙把Global.asax作为威胁拒绝了(笔者估计是浏览器拒绝了上传提交)。

目前看,问题部分获得解决。但是否还有加密后的文本被防火墙或浏览器视为威胁,不得而知。根本上,目前还不清楚到底是浏览器还是防火墙或Windows Server2012拒绝了WebApi访问。当然,可以与网管协商放开防火墙做测试看看。不过防火墙由用户方控制,涉及到云服务等的安全,一般不会放开。

碰到一堵墙时,可以找人开个口子,也可以搭个梯子翻过去。呵呵,笔者采用了后一种方法。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/621865
推荐阅读
相关标签
  

闽ICP备14008679号