最近在作一个先后端彻底分离模式下,ueditor编辑器多图上传的优化,遇到一些似是而非的坑。html
以前采用的跨域方案是用postMessage,大体先将图片上传事件发回接口所在的同域工程,再将获得的上传结果返回给静态域名下的编辑器,实现编辑器代码与后端工程彻底分离。前端
此次场景有点不一样,因为主体入口文件被后端工程直接引用,但ueditor的不少弹窗页面都是引用于静态域名下的页面(包括多图片上传模块),这形成了单图上传OK,多图上传模块跨域的状况。此次的思路是想经过CORS直接跨域访问接口,下面把主要问题记录以下:html5
因为通常公司的主域都是相同的,能够经过设置domain为相同的主域来实现(两边都要设置)java
// 弹窗跨域设置 document.domain = window.location.hostname.match(/(\w+.com)$/)[0]; // 正则获取到主域名
在作demo测试时,用form表单实现跨域图片提交,无心中发现了一个后端安全拦截策略,代码以下:jquery
// form所在的iframe的域名是:http://www.xx.com:8000 <form action="http://www.yy.com:9000/upload" enctype="multipart/form-data" method="post"> <!-- 若是接口支持多图,加上multiple="multiple" --> <input type="file" name="imageFile"> <!-- 其它参数 --> <input type="hidden" name="uploadType" value="p"> </form>
当执行提交时,控制台若显示'X-Frame-Options' to 'deny'之类的提示时,后研究发现iframe页面的请求被后台框架的iframe安全策略拦截,而缘由是因为当前请求用iframe下form提交的,然后台框架正好作了DENY或默认(便是拒绝)的安全策略。web
解决方案:
一、将form表单提交换成下文(3)中的ajax/formData的方法
二、修改后台安全配置,如java,须要修改spring安全配置文件:src/main/resources/spring-security.xmlajax
policy有三个值:DENY(默认值), SAMEORIGIN(同域), ALLOW-FROM(容许指定值,chrome貌似支持性很差)spring
<frame-options policy="SAMEORIGIN" /> // 或下面这样设置,但实际测试感受二者效果差很少 <frame-options policy="SAMEORIGIN" strategy="whitelist" value="http://www.xx.com:8000" />
当设置ALLOW-FROM时,因为chrome支持性很差,控制台会出现'ALLOW-FROM DENY' is not a recognized directive. The header will be ignored(没法识别指令,头信息被忽略),效果同上面的SAMEORIGIN。chrome
注意的是:若是是ajax形式的提交,则不受上面任何策略的影响(包括DENY)。
response.setHeader("Access-Control-Allow-Origin", "http://www.xx.com:8000"); response.setHeader("Access-Control-Allow-Credentials", "true");
通常状况下,设置上面的头后,cors跨域基本上能成功了。json
须要注意的是,当设置了Credentials,上面的Origin值不能再为*了,须要指定一个具体的值。
在跨域时,若出现options类型的请求,大概情形分如下两种:
1.) 非简单跨域请求
http简单请求包括GET、POST、HEAD三种,像PUT、DELETE、OPTIONS就属于非简单请求,另外要注意的是,当POST请求时,Content-Type必须是text/plain、application/x-www-form-urlencoded、 multipart/form-data中的一个值,若为application/json时,则不属于简单请求,会出现options预检。
2.) 自定义HTTP头部
当前端在跨域时经过xhr.setRequestHeader自定义了一个头,在发起正式的请求前,会产生一个OPTIONS预检请求,该请求中会有两个字段:Access-Control-Request-Method和Access-Control-Request-Headers,分别描述了使用的方法和自定义了哪些头信息。
举个粟子,以下(前端使用了delete,自定义头Test和非简单请求application/json):
$.ajax({ ... type: 'DELETE', // 此处大小写均可以,但后台的Access-Control-Allow-Methods里必定要大写 beforeSend: function (xhr) { xhr.setRequestHeader('Test', 'myValue'); // 添加了自定义头 }, contentType: 'application/json', // contentType内容非简单请求
发起options预检请求后,能够从Request Headers中能够看出使用的方法和自定义了哪些头:
Access-Control-Request-Headers: content-type,test Access-Control-Request-Method: DELETE
则后端java添加相应的容许:
response.AddHeader("Access-Control-Allow-Origin": "*"); response.AddHeader("Access-Control-Allow-Methods", "DELETE"); // 此处值必定要大写,OPTIONS自己值不用加 response.AddHeader("Access-Control-Allow-Headers", "Content-Type, Test"); // 此处值大小写无所谓
后端有没有设置成功或有没有进入方法里能够从options预检请求的Response Headers里看出。
Access-Control-Allow-Headers:content-type, test Access-Control-Allow-Methods:DELETE
// 经过FormData将文件转成二进制数据 var formData = new FormData(); formData.append('imageFile', file); $.ajax({ // UE.Editor.prototype.apiDomain把java的接口域名挂在UE上读取了 url: UE.Editor.prototype.apiDomain + '/upload', type: 'POST', data: formData, // 上传formdata封装的数据 dataType: 'JSON', cache: false, // 不缓存 processData: false, // jQuery不要去处理发送的数据 xhrFields: { withCredentials: true // 前端设置是否带cookie }, contentType: false, // jQuery不要去设置Content-Type请求头 success: function(res) { console.log(res); }, error(res) { console.log(res); } });
注意:processData,contentType都需设置false,否则jquery会作处理;另外若接口不支持多文件上传,可定时循环发送图片。
实现图片预览有两种方法:
方法一:
FileReader,使用的base64方式实现
<input type="file" name="imageFile"> <script> document.querySelector('input[type=file]').onchange = function(e) { var file = e.target.files[0]; var fileReader = new FileReader(); fileReader.onload = function(e) { var image = new Image(); image.src = e.target.result; document.body.append(image); } fileReader.readAsDataURL(file); } </script>
方法二:
经过html5的window.URL.createObjectURL方法,使用的是blob的方式实现。
使用方法:window.URL.createObjectURL(blob || file);
兼容写法:window[window.webkitURL ? 'webkitURL' : 'URL'].createObjectURL.(blob || file);
<input type="file" name="uploadFile"> <script> document.querySelector('input[type=file]').onchange = function(e) { var file = e.target.files[0]; var url = window.URL.createObjectURL(file); var image = new Image(); image.src = url; document.body.append(image) } </script>
当图片临时url被替换成正式url时,记得销毁临时url,方法:
window.URL.revokeObjectURL(url);