editormd 跨域上传图片的问题解决方式 以及 Uncaught DOMException: Blocked a frame with origin from accessing a cross-origin frame 报错的原因和解决方法
editormd 解决 图片上传的问题的方式有点绕, 他的图片是通过表单方式提交,但是表单提交成功后会刷新页面,为了解决页面刷新的问题,editormd将表单提交的响应提交给了一个子 iframe标签,然后通过找到该iframe标签,获取该标签的body,得到内容,转换后复制给父窗口的相关标签.
这样做通常是没问题的,同域上传不会出现问题,但是跨域上传的时候就会报错,如标题.
因为浏览器有个同源策略,iframe本质上相当于一个新的页面,有自己的url地址,当父窗口要访问获取子窗口的内部数据时,如果两个窗口的域名不一致(域名,IP,端口任一不同),那么就会报出该安全错误.
editormd提供了一个跨域上传的方案,就是配置时提供一个回传地址,我考虑的意思是,editormd将结果放在该会传地址中,该回调地址的域名与父窗口一致,那么就可以访问该iframe内部的数据了.
但是可能因为我哪里没弄对,依然报错如上.
所以我决定修改editormd的插件代码.
原文件中相关代码如下: 构建代码部分
1 var action = settings.imageUploadURL + (settings.imageUploadURL.indexOf("?") >= 0 ? "&" : "?") + "guid=" + guid; 2 3 if (settings.crossDomainUpload) 4 { 5 action += "&callback=" + settings.uploadCallbackURL + "&dialog_id=editormd-image-dialog-" + guid; 6 } 7 8 var dialogContent = ( (settings.imageUpload) ? "<form action=\"" + action +"\" target=\"" + iframeName + "\" method=\"post\" enctype=\"multipart/form-data\" class=\"" + classPrefix + "form\">" : "<div class=\"" + classPrefix + "form\">" ) + 9 ( (settings.imageUpload) ? "<iframe name=\"" + iframeName + "\" id=\"" + iframeName + "\" guid=\"" + guid + "\"></iframe>" : "" ) + 10 "<label>" + imageLang.url + "</label>" + 11 "<input type=\"text\" data-url />" + (function(){ 12 return (settings.imageUpload) ? "<div class=\"" + classPrefix + "file-input\">" + 13 "<input type=\"file\" name=\"" + classPrefix + "image-file\" accept=\"image/*\" />" + 14 "<input type=\"submit\" value=\"" + imageLang.uploadButton + "\" />" + 15 "</div>" : ""; 16 })() + 17 "<br/>" + 18 "<label>" + imageLang.alt + "</label>" + 19 "<input type=\"text\" value=\"" + selection + "\" data-alt />" + 20 "<br/>" + 21 "<label>" + imageLang.link + "</label>" + 22 "<input type=\"text\" value=\"http://\" data-link />" + 23 "<br/>" + 24 ( (settings.imageUpload) ? "</form>" : "</div>");
8到15行为上传图片的代码,这部分代码首先出现的问题是csrftoken的问题, 表单提交需要在请求头或表单内添加csrftoken字段,这部分 editormd没有做好,除此之外其他部分可以看到传图的原理:
<form action=\"" + action +"\" target=\"" + iframeName + "\" method=\"post\" 可以看到是把新页面指向了子iframe(后面加上的),
然后就是获取iframe内容的部分:
1 var submitHandler = function() { 2 3 var uploadIframe = document.getElementById(iframeName); 4 5 uploadIframe.onload = function() { 6 7 loading(false); 8 9 var body = (uploadIframe.contentWindow ? uploadIframe.contentWindow : uploadIframe.contentDocument).document.body; 10 var json = (body.innerText) ? body.innerText : ( (body.textContent) ? body.textContent : null); 11 12 json = (typeof JSON.parse !== "undefined") ? JSON.parse(json) : eval("(" + json + ")"); 13 14 if(!settings.crossDomainUpload) 15 { 16 if (json.success === 1) 17 { 18 dialog.find("[data-url]").val(json.url); 19 } 20 else 21 { 22 alert(json.message); 23 } 24 } 25 26 return false; 27 }; 28 };
这部分代码先获取iframe元素,然后得到内部文本,转换为object对象,判断响应状态,赋值给Input标签.
问题就出在获取子iframe元素内容上,基于同源策略,浏览器不让获取.
解决方法也很简单,但是同样花费了我一晚上加一上午的时间(主要是方向错了,前期主要精力放在解决iframe跨域的问题,发现不好解决,然后换了个思路,决定改变插件的传图方式为ajax)
下面是修改后的代码(红色部分为修改的部分):
1 var CallBackJS = "<script>" + 2 "function mdUpload(file) {" + 3 "upload_file(file, function(pic, url) { json = {success:1,url:url,message:'OK'};document.getElementById('json-body-ht').innerText = JSON.stringify(json) })" + 4 " }" + 5 "</script>"; 6 7 var dialogContent = ( (settings.imageUpload) ? "<form action=\"" + action +"\" target=\"" + iframeName + "\" method=\"post\" enctype=\"multipart/form-data\" class=\"" + classPrefix + "form\">" : "<div class=\"" + classPrefix + "form\">" ) + 8 ( (settings.imageUpload) ? "<iframe name=\"" + iframeName + "\" id=\"" + iframeName + "\" guid=\"" + guid + "\"></iframe>" : "" ) + 9 "<label>" + imageLang.url + "</label>" + 10 "<input type=\"text\" data-url />" + (function(){ 11 return (settings.imageUpload) ? "<div class=\"" + classPrefix + "file-input\">" + 12 "<pre id='json-body-ht' style='display: none'></pre>" + 13 "<input type='hidden' name='csrf_token' value='" + CSRFTOKEN + "' />" + 14 "<input onchange='mdUpload(this.files[0])' type=\"file\" name=\"" + classPrefix + "image-file\" accept=\"image/*\" />" + 15 "<input type=\"submit\" value=\"" + imageLang.uploadButton + "\" />" + 16 "</div>" + CallBackJS : ""; 17 })() + 18 "<br/>" + 19 "<label>" + imageLang.alt + "</label>" + 20 "<input type=\"text\" value=\"" + selection + "\" data-alt />" + 21 "<br/>" + 22 "<label>" + imageLang.link + "</label>" + 23 "<input type=\"text\" value=\"http://\" data-link />" + 24 "<br/>" + 25 ( (settings.imageUpload) ? "</form>" : "</div>");
1 var submitHandler = function() { 2 3 var uploadIframe = document.getElementById(iframeName); 4 5 uploadIframe.onload = function() { 6 7 loading(false); 8 9 // var body = (uploadIframe.contentWindow ? uploadIframe.contentWindow : uploadIframe.contentDocument).document.body; 10 var body = document.getElementById('json-body-ht'); 11 var json = (body.innerText) ? body.innerText : ( (body.textContent) ? body.textContent : null); 12 13 console.log(json); 14 json = (typeof JSON.parse !== "undefined") ? JSON.parse(json) : eval("(" + json + ")"); 15 console.log(json.success, json.success === 1); 16 if(!settings.crossDomainUpload) 17 { 18 if (json.success === 1) 19 { 20 dialog.find("[data-url]").val(json.url); 21 } 22 else 23 { 24 alert(json.message); 25 } 26 } 27 28 return false; 29 }; 30 };
修改的部分主要四个地方:
- 添加csrftoken 这部分后面极可能会多余,临时只是解决插件自己的表单提交, 添加接受数据的元素
- 添加监听事件
- 添加回调函数
- 修改元素获取方式
upload_file是我定义的全局上传图片方法,通过axios方式上传,参数为文件对象和回调,具体内部代码 下面.
主要操作是按照插件之前的规则将相应数据填充给自定义元素,自定义元素在父窗口中,直接调用不会出现跨域的问题
将插件源代码中获取数据的方式从 iframe 获取 改为 自定义元素获取.
我一个后端对前端的东西不是很懂,经验不足,前期花费大量时间在解决 iframe跨域上,所以有时候 方向 也很重要.
========================================================================================
有些小伙伴都不看上面那部分红字,upload_file是我自定义的方法,自定义的!!!!!!
放在下面你们参考下吧.
该方法适用于我自己的后端,如果适配你的后端,注意看看响应值是不是要修改,这个方法不建议直接拷贝就用,这是针对我自己的项目测试可用的,对其他项目有许多东西可能会提示未定义
1 function upload_file(file, call_back, b64_type = false) { 2 // file 要上传的文件 3 // call_back 回调函数 4 // b64_type 是否以base64上传 5 var axios_form = new FormData(); 6 axios_form.append('file', file); 7 // 以下为将文件转化为 base64 字符串 8 if (b64_type) { 9 var fileB64 = new FileReader(); 10 fileB64.readAsDataURL(imgfile); 11 fileB64.onload = function () { 12 var b64data = fileB64.result; 13 var b64str = b64data.slice(b64data.indexOf(',') + 1); 14 axios_form.append('b64', b64str); 15 axios({ 16 url: api_urls.UpLoadImg + '?b64=1', 17 method: 'post', 18 data: {b64: b64str}, 19 headers: {'Content-Type': 'application/json'} 20 }).then(function (resp) { 21 data = resp.data; 22 url = data.data.path; 23 pic = data.data.name; 24 console.log(data); 25 rc = data.status; 26 call_back(pic, url); 27 }).catch(function (error) { 28 console.log(error); 29 }); 30 }; 31 } else { 32 axios({ 33 url: api_urls.UpLoadImg, 34 method: 'post', 35 data: axios_form, 36 headers: {'Content-Type': 'multipart/form-data'} // 上传文件的请求头 数据格式 37 // headers:{'Content-Type': 'application/x-www-form-urlencoded'} // 上传form表单的请求头 数据格式 38 }).then(function (resp) { 39 data = resp.data; 40 url = data.data.path; 41 pic = data.data.name; 42 console.log(data); 43 rc = data.status; 44 call_back(pic, url); 45 }).catch(function (error) { 46 console.log(error); 47 }); 48 } 49 }