赞
踩
笔者最近写了一个通过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:
- @model CSUST.Files.TDirItems
-
- @{
- Layout = null;
- }
-
- <!DOCTYPE html>
- <html>
- <head>
- <title>文件上传</title>
- <script type="text/javascript" src='@Url.Action("jquery-1.12.4.min.js", "scripts")'></script>
- <script type="text/javascript" src='@Url.Action("jquery.base64.min.js", "scripts")'></script>
-
- <script type="text/javascript">
-
- var fileData = [];
-
- $(document).ready(function ()
- {
- $("#tbFileName1").on("change", function(){
- GetFile(this);
- });
-
- $("#tbFileName2").on("change", function () {
-
- GetFile(this);
- });
- });
-
-
- function GetFile(fileObj)
- {
- fileData[fileObj.id] = null;
-
- var fileName = $(fileObj).val();
- if (fileName == null || fileName == "") // 文件名为空
- {
- return;
- }
-
- var files = $(fileObj).get(0).files;
- if (files[0].size > 4 * 1024 * 1024)
- {
- alert("单个文件不能大于4M。");
- $(fileObj).val("");
- return;
- }
-
- var reader = new FileReader();
- reader.readAsDataURL(files[0]);
-
- reader.onload = function ()
- {
- var base64 = reader.result;
- var n = base64.indexOf("base64,");
- fileData[fileObj.id] = base64.substr(n + 7);
- }
- }
-
- function Clear()
- {
- $("#tbFileName1").val("");
- $("#tbFileName2").val("");
- }
-
- function Upload()
- {
- var f1 = $("#tbFileName1").val();
- var f2 = $("#tbFileName2").val();
-
- if ((f1 == null || f1 == "") && (f2 == null || f2 == ""))
- {
- alert("至少要上传一个文件。");
- return;
- }
-
- if (f1 != "" && fileData["tbFileName1"] == null)
- {
- alert("尚未读取文件1,稍后!");
- return;
- }
-
- if (f2 != "" && fileData["tbFileName2"] == null)
- {
- alert("尚未读取文件2,稍后!");
- return;
- }
-
- if(f1 == f2)
- {
- alert("不能上传相同文件。");
- return;
- }
-
- var n1 = $("#tbTicket").val();
- var n2 = $("#ckAllowNewFiles").is(":checked");
- var n3 = $("#cbDirNameKeys").val();
-
- if (n1 == "")
- {
- alert("必须输入验证口令。");
- return;
- }
-
- if (n3.indexOf("\\") == 0)
- {
- alert("不能选择\\注释格式的文件夹项。");
- return;
- }
-
- var formData = new FormData();
-
- formData.append("VerifyTicket", n1);
- formData.append("AllowNewFiles", n2);
- formData.append("DirNameKey", n3);
-
- if (f1 != "")
- {
- formData.append("FileName1", $.base64('encode', f1));
- formData.append("FileContent1", fileData["tbFileName1"]);
- }
-
- if (f2 != "")
- {
- formData.append("FileName2", $.base64('encode', f2));
- formData.append("FileContent2", fileData["tbFileName2"]);
- }
-
- SendFiles(formData);
- }
-
- function SendFiles(formData)
- {
- $.ajax({
- type: "post",
- url: '@Url.Action("Upload", "Api/FilesApi")',
- async: false,
- data: formData,
- contentType: false,
- processData: false,
- success: function (data, status)
- {
- if (status != "success")
- {
- alert("上传文件失败: " + status);
- return;
- }
-
- if (data == null)
- {
- alert("上传文件失败, 没有返回结果。");
- return;
- }
-
- if (data.IsFailed == true)
- {
- alert(data.ErrorMessage);
- return;
- }
-
- alert(data.Note);
-
- $("#tbFileName1").val("");
- $("#tbFileName2").val("");
- },
- error: function (xhr, status, err)
- {
- alert("上传文件异常: " + status + "," + err);
- }
- });
- }
- </script>
- </head>
- <body>
- <form id="Form1">
- <div align="center">
- <h2><label>@Model.WebSiteTitle</label></h2>
- <table style="width: 1050px;" border="1">
- <tr style="height: 32px">
- <td rowspan="2" style="width: 120px;text-align:center">
- 文件名
- </td>
- <td colspan="2" align="left">
- <input ID="tbFileName1" name="tbFileName1" type="file" style="width: 96%;"/>
- </td>
- </tr>
- <tr style="height: 32px">
- <td colspan="2" align="left">
- <input ID="tbFileName2" name="tbFileName2" type="file" style="width: 96%;"/>
- </td>
- </tr>
- <tr style="height: 42px">
- <td style="text-align: center">到文件夹</td>
- <td style="width: 650px; text-align: left;">
- <select ID="cbDirNameKeys" style="width: 650px;">
- @foreach(CSUST.Files.TDirItem item in Model.DirItems)
- {
- <option>@item.DirNameKey</option>
- }
- </select>
- </td>
- <td style="width: 280px; text-align:left;">
- <input type="checkbox" ID="ckAllowNewFiles" />
- <label for="ckAllowNewFiles">新增cshtml.css.js.jpg等文件</label>
- </td>
- </tr>
- <tr style="height: 42px;">
- <td style="height: 42px; text-align: center;">校验口令</td>
- <td style="height: 42px; text-align: left;">
- <input type="password" id="tbTicket" style="width: 645px;" />
- </td>
- <td style="height: 42px; text-align:center;">
- <input type="button" ID="bnUpload" value="上传文件" οnclick="Upload()" style="width: 96px; height: 32px;"/>
- <input type="button" ID="bnClearFile" value="清空文件" οnclick="Clear()" style="width: 96px; height: 32px;" />
- </td>
- </tr>
- </table>
- </div>
- </form>
- </body>
- </html>
如下是服务器端ASP.NET MVC4的WebApi对应的POST方法Upload:
- using System;
- using System.Collections.Generic;
- using System.Web;
- using System.Web.Http;
-
- namespace CSUST.Files
- {
- public class FilesApiController : ApiController
- {
- [HttpPost]
- public CSUST.Web.TWebApiResult Upload()
- {
- try
- {
- var httpRequest = HttpContext.Current.Request;
-
- var dirNameKey = httpRequest.Form["DirNameKey"];
- var allowNewFiles = httpRequest.Form["AllowNewFiles"];
- var verifyTicket = httpRequest.Form["VerifyTicket"];
-
- if (dirNameKey.StartsWith(TDirItem.NoteChars) == true)
- {
- return new Web.TWebApiResult("不能选择" + TDirItem.NoteChars + "注释格式的文件夹项。");
- }
-
- TDirItems dirItems = new TDirItems();
- if (dirItems.VerifyTicket != verifyTicket)
- {
- return new Web.TWebApiResult("验证口令错误。");
- }
-
- List<string> fileNames = new List<string>();
- List<string> fileContents = new List<string>();
-
- if (string.IsNullOrWhiteSpace(httpRequest.Form["FileName1"]) == false)
- {
- string fileName1 = this.GetFileNameByBase64(httpRequest.Form["FileName1"]);
- fileNames.Add(fileName1);
- fileContents.Add(httpRequest.Form["FileContent1"]);
- }
-
- if (string.IsNullOrWhiteSpace(httpRequest.Form["FileName2"]) == false)
- {
- string fileName2 = this.GetFileNameByBase64(httpRequest.Form["FileName2"]);
- fileNames.Add(fileName2);
- fileContents.Add(httpRequest.Form["FileContent2"]);
- }
-
- CSUST.Text.TStringItemsBuilder sb = new Text.TStringItemsBuilder(Environment.NewLine);
- foreach (var fn in fileNames)
- {
- if (dirItems.IsAllowedFileName(dirNameKey, fn) == false)
- {
- sb.AppendItem(fn + "不能保存到指定的文件夹中。");
- }
-
- var saveFileName = dirItems.GetSavedFileName(dirNameKey, fn);
-
- if (System.IO.File.Exists(saveFileName) == false && allowNewFiles.ToUpper() != "TRUE")
- {
- sb.AppendItem(fn + "新文件时必须勾选新增复选框。");
- }
- }
-
- if (sb.Length > 0)
- {
- return new Web.TWebApiResult(sb.ToString());
- }
-
- sb.Clear();
- sb.AppendItem("保存文件成功:");
- for (int k = 0; k < fileNames.Count; k++)
- {
-
- var saveFileName = dirItems.GetSavedFileName(dirNameKey, fileNames[k]);
- byte[] fb = Convert.FromBase64String(fileContents[k]);
- using (System.IO.FileStream fs = new System.IO.FileStream(saveFileName, System.IO.FileMode.Create, System.IO.FileAccess.Write))
- {
- fs.Write(fb, 0, fb.Length);
- fs.Flush();
- sb.AppendItem(saveFileName);
- }
- }
-
- CSUST.Web.TWebApiResult r = new Web.TWebApiResult() { Note = sb.ToString() };
- return r;
- }
- catch (Exception err)
- {
- return new CSUST.Web.TWebApiResult(err, true);
- }
- }
-
- private string GetFileNameByBase64(string base64FileName)
- {
- byte[] b = Convert.FromBase64String(base64FileName);
- string fileName = System.Text.Encoding.UTF8.GetString(b); // 前端使用了base64加密,防止文本串被防火墙拒绝
- return System.IO.Path.GetFileName(fileName);
- }
- }
- }
经过上述技术处理后提交一般文件正常,但在上传Global.asax文件时,仍然抛出上述异常。测试时把该文件改名为@@Global.asax则可正常上传。显然,浏览器或防火墙把Global.asax作为威胁拒绝了(笔者估计是浏览器拒绝了上传提交)。
目前看,问题部分获得解决。但是否还有加密后的文本被防火墙或浏览器视为威胁,不得而知。根本上,目前还不清楚到底是浏览器还是防火墙或Windows Server2012拒绝了WebApi访问。当然,可以与网管协商放开防火墙做测试看看。不过防火墙由用户方控制,涉及到云服务等的安全,一般不会放开。
碰到一堵墙时,可以找人开个口子,也可以搭个梯子翻过去。呵呵,笔者采用了后一种方法。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。