複雜場景下的跨域圖片上傳

最近在作一個先後端徹底分離模式下,ueditor編輯器多圖上傳的優化,遇到一些似是而非的坑。html

以前採用的跨域方案是用postMessage,大體先將圖片上傳事件發回接口所在的同域工程,再將獲得的上傳結果返回給靜態域名下的編輯器,實現編輯器代碼與後端工程徹底分離。前端

此次場景有點不一樣,因爲主體入口文件被後端工程直接引用,但ueditor的不少彈窗頁面都是引用於靜態域名下的頁面(包括多圖片上傳模塊),這形成了單圖上傳OK,多圖上傳模塊跨域的狀況。此次的思路是想經過CORS直接跨域訪問接口,下面把主要問題記錄以下:html5

一、跨域iframe之間的js代碼相互調用

因爲通常公司的主域都是相同的,能夠經過設置domain爲相同的主域來實現(兩邊都要設置)java

// 彈窗跨域設置
document.domain = window.location.hostname.match(/(\w+.com)$/)[0]; // 正則獲取到主域名

二、iframe頁面的跨域訪問攔截

在作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)。

三、CORS跨域後臺設置

response.setHeader("Access-Control-Allow-Origin", "http://www.xx.com:8000");
response.setHeader("Access-Control-Allow-Credentials", "true");

通常狀況下,設置上面的頭後,cors跨域基本上能成功了。json

須要注意的是,當設置了Credentials,上面的Origin值不能再爲*了,須要指定一個具體的值。

四、OPTIONS請求preflight(預檢)

在跨域時,若出現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

五、經過ajax/formData批量上傳

// 經過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會作處理;另外若接口不支持多文件上傳,可定時循環發送圖片。

六、圖片預覽臨時URL的生成

實現圖片預覽有兩種方法:

方法一:
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);
相關文章
相關標籤/搜索