因为浏览器同源战略,通常发送要求url的协定、域名、端口三者之间恣意一与当前页面地点差别即为跨域
近来项目要兼容IE9,找了一些材料,实践了一下,如今总结一下,防止今后踩坑。
一般要求的跨域
简朴粗犷的处理计划
第一次遇到这个题目,所以就是上网找找有无什么好的处理计划。最初找到的计划是如许的,直接在IE中设置设置受信托的站点,然后许可其能够举行跨域接见,末了在jQuery
中设置开启跨域要求。oh,No!这么粗犷,好吧,这也是一个不是要领的要领,假如做的是一个小项目,用户不多,那直接写在用户手册里,让他们本身去配吧。然则,这明显不是一个好的处理计划啊,那只能继承找了。
XDomainRequest(XDR)处理计划
ok,微软在IE8
和IE9
下给我们供应了XDomainRequest
来举行处理跨域题目,官方的文档能够在 这里看到。固然Github
上也有开源的jQuery
插件,能够在这里找到。
XDR
的限定:
XDR
仅支撑GET
与POST
这两种要求体式格局,虽然能够运用上面提交的插件来处理前端部份只需举行简朴修正代码就能够提交PUT
/HEAD
/DELETE
的要求的题目,然则其要求的发作出去照旧照样将PUT
/HEAD
/DELETE
转化为POST
,将HEAD
转化为GET
要求。当是POST
要求的时刻,要求计划会以__method=原要求
的体式格局组织到场到要求体的body
中。当是HEAD
要求的时刻,要求计划会以__method=原要求
的体式格局组织到场要求url的查询参数中。如今大部份API开辟都是依据RESTful
范例举行设想的,假如是本身的效劳端还好,能够叫效劳端的同砚增加一个阻拦器做一个阻拦推断,然后实行对应的要领(ps:我想过去应当是这个模样,不知道效劳端的同砚会不会磨刀子)。然则假如你挪用是网上的API的接口的话,那就心有余而力不足了。XDR不支撑自定义的要求头
,因而假如你的效劳端是用过header
中的自定义参数举行做身份验证的话,那也行不通了。要求头的
Content-Type
只许可设置为text/plain
XDR
不许可跨协定的要求,假如你的网页是在HTTP
协定下,那末你只能要求HTTP
协定下的接口,不能接见HTTPS
下的接口。XDR
只吸收HTTP/HTTPS
的要求提议要求的时刻,不会照顾
authentication
或cookies
JSONP
JSONP
的实质是动态的加载<script>
标签,因而其只支撑GET
要求而不支撑其他范例的HTTP
要求。
JSONP
的实行历程大抵以下:
客户端设置一个全局的
function
,然后运用callback=function
的要领,将回调的要领通报给效劳端。比方:// 定义全局函数 function showData (data) { console.log(data) } var url = "http://test.com/jsonp/query?id=1&callback=showData" // 这个就是script标签中的url
效劳端在吸收到要求的时刻,天生一个动态的
js
剧本,在该剧本中,挪用callback
参数通报进来的function
,将回来返回的json
数据已参数的情势去通报给该function
,如许,客户端在加载这个js
的时刻,就会自动去实行了。
代办
实在,跨域的根本题目就在于,你挪用的效劳端地点web地点
不在同一个域下,那末,我们最轻易想到的一个处理计划就是:那我把他们放在一个域下面不就能够了么。因而我们能够在web
工程下 安排一个代办效劳器,在IE10
以下的浏览器中,我们的收集要求统一走这一个代办接口,由效劳器带我们去转发这个HTTP
要求,然后再将效果返回给我们。
事实上我们项目中也是采纳的这个计划,我们定义了一个接口:
URL: v0.1/dispatcher
要领: POST
要求内容:
{
"request_url":"http://test.com", //必填,要求url
"request_method":"POST", //必填,要求要领:GET/PUT/PATCH/POST/DELETE
"request_headers":{
"Content-Type":["application/json"]
}, //选填,要求头
"request_data":{
"data":{
//要求body
}
}
} //选填,要求body
效劳端经由过程客户端传来的这些参数去组织一个HttpClient
,提议要求。
文件上传的题目
既然经由过程上面的代办接口处理了,IE10
一下的跨域要求题目,本想着应当没什么题目了,试了试项目中的文件上传,oh,no!不能运转,看了看我们的文件上传,是经由过程本身new FormData()
的体式格局去处效劳器POST
要求的。然后翻找了一下webApi, 发明从IE10
最先兼容的,这就……,而且XMLHttpRequest
的send(formData)
这个要领也是从IE10
最先支撑的。那没要领了只能寻觅其他的要领了。
隐式表单上传
找到老司机,请教了一下,初期IE都是用运用隐式的iframe
中包括一个form
表单,然后直接去提交form
表单。然后效劳完整返回的数据在iframe
中,经由过程js
代码去内里猎取iframe中的数据,作为返回值。
然后从老司机那里获得一份插件ajaxfileupload,另有一个就是本身在Github
上找的一个jQuery-File-Upload,如今就来讲讲这两个插件
ajaxfileupload
实用于效劳器返回的数据是文本花样
这份代码也很简朴就200多行,重要就头脑就是依据上面说的,运用隐式的iframe
嵌套form
表单来完成上传操纵。然则呢?这个插件只适合在效劳器返回数据是文本数据的时刻,假如效劳器返回的是json
的数据,IE10
一下的浏览器就会自动去实行下载操纵,js
代码在实行到下载的时刻就中断了,并不会继承往下实行了。所以也不是很实用。假如效劳器支撑返回数据花样是文本花样的话,这个组件照样挺好用的。
// 基础用法以下
<!-- 隐蔽file标签 -->
<input id="fileUpload" style="display: none" type="file" name="file">
//挑选文件以后实行上传
$('#fileUpload').on('change', function() {
$.ajaxFileUpload({
url:'http://test.com',
secureuri:false,
fileElementId:'fileToUpload',//file标签的id
dataType: 'json',//返回数据的范例
data:{name:'logan'},//一同上传的数据
success: function (data, status) {
console.log(data)
},
error: function (data, status, e) {
alert(e);
}
});
});
jQuery-File-Upload
实用于效劳器返回的数据是JSON花样切支撑重定向
这个插件呢,对照ajaxfileupload
他斟酌到了这类返回json
的状况,然则它的运用须要效劳端举行支撑,其重要头脑照样运用了隐式的表单上传文件,然则它是经由过程效劳其的重定向来吸收数据的,效劳器吸收到了客户端的要求以后,将返回的数据经由过程URLEncode
以后,拼接在前端web
页面的背面,然后在页面中剖析数据,写到body
中,用jQuery
去猎取这些数据。
详细用法以下:
如今效劳器组织一个吸收返回数据的页面result.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>result</title>
</head>
<body>
<script>
var href = window.location.href
var search = href.slice(href.indexOf('?') + 1)
document.body.innerText=document.body.textContent=decodeURIComponent(search)
</script>
</body>
</html>
然后本身定义一个上传的组件,我这里是运用Vue
来包装成一个组件的
<template>
<div class="c-uploader"
v-tap
@click="onTap">
<input
type="file"
ref="file"
id="fileUploadNormal"
name="file"
style="display: none"
data-sequential-uploads="true"
:accept="accept"
multiple="false"/>
<slot></slot>
</div>
</template>
<script>
import { getMACContent } from '../../utils/tokens'
import optionsUtil from '../../utils/optionsUtil'
import tap from '../directives/tap'
import '../libs/vendor/jquery.ui.widget'
import '../libs/jquery.iframe-transport'
import '../libs/jquery.fileupload'
import '../libs/cors/jquery.xdr-transport'
import g_config from '../../config/config'
export default{
props: {
url: {
type: String,
required: true
},
data: {
type: Object,
default: function () {
return {}
}
},
accept: {
type: String,
default: '*'
},
onSuccess: {
type: Function,
default: function () {
}
},
onError: {
type: Function,
default: function () {
}
},
checkFile: {
type: Function,
default: function () {
return true
}
}
},
data () {
return {
uploadFile: null,
headers: {
Authorization: new getMACContent({url: this.url,method: 'POST'})._value.returnMessage
}
}
},
methods: {
onTap (e) {
const fileInputEl = this.$refs.file
if (!fileInputEl) {
return
}
// TODO: trigger tap or touch but not click ?
fileInputEl.click()
fileInputEl.value = ''
},
init () {
this.uploadFile = null
},
startUpload () {
const that = this
if (this.uploadFile !== null) {
if (this.checkFile(this.uploadFile.files[0])) {
that.data.request_url = that.url
that.data.name = this.uploadFile.files[0].name
that.data.redirect = location.protocol+'//' + location.host+'/result.html?'
that.data.authorization = that.headers.Authorization
$('#fileUploadNormal').fileupload({
url: g_config.dispatch_url + '/v0.1/dispatcher/upload',
formData: that.data
})
that.uploadFile.submit() // 上传文件
}
} else {
const response = {
msg: '请挑选须要上传的文件'
}
this.onError(null, response, null)
}
}
},
computed: {},
watch: {},
components: {
},
directives: {
tap
},
mounted () {
const that = this
$('#fileUploadNormal').fileupload({
dataType: 'json', // 设置返回数据花样
multiple: false, // 只许可挑选单文件
iframe: true, // 运用iframe
sequentialUploads: true,
forceIframeTransport : true, // 强迫运用iframe
autoUpload: false, // 封闭自动上传,否则在文件变化的时刻,就会自动upload
formData: that.data, // 定义须要分外上传的数据
replaceFileInput: false,
add: function (e, data) {
that.headers.Authorization = new getMACContent({url: that.url,method: 'POST'})._value.returnMessage
that.uploadFile = data // 记录下数据
},
done: function (e, data) {
const response = data.result
const resultData = JSON.parse(response.data)
if (response.result.toUpperCase() === 'SUCCESS') {
that.onSuccess(resultData)
} else {
that.onError(null,resultData, null)
}
},
fail: function (e, data) {
that.uploadFile = null
that.onError(null,{
msg: '上传失利'
}, null)
}
})
}
}
</script>
这个插件是依靠jQuery
的,而且依靠jQuery-UI
,另有要注重的是在IE10
以下的版本都要引入jquery.iframe-transport
与jquery.xdr-transport
我代码中发送数据的体式格局是它在add
要领中返回的data
数据,经由过程该对象去直接上传文件,这时候上传的FormData
的文件信息中,文件底本是什么范例就是什么范例了,这是我们所希冀的。我之前检察官方的文档,还运用过另一种体式格局
var jqXHR = $('#fileupload').fileupload('send', {files: filesList})
.success(function (result, textStatus, jqXHR) {/* ... */})
.error(function (jqXHR, textStatus, errorThrown) {/* ... */})
.complete(function (result, textStatus, jqXHR) {/* ... */});
上传的时刻运用的是如许的体式格局,发明FormData
中上传文件的范例变为了Content-Type: application/octet-stream
,然后效劳器就剖析不到数据了。所以照样引荐用它原生的submit
体式格局去提交数据。
注重
这两个插件的实质照样运用form
表单上传文件,因而我们没法增加自定义的header
头,而且假如本来的效劳器不支撑要求重定向的话怎么办,那就没有要领运用jQuery-File-Upload
这个插件了。所以最稳妥的体式格局,照样在我们当地做了一层代办,由代办去发作真正的要求。
下面给出重要的转发FormData
的java
代码
public ResponseEntity dispatcherUpload(HttpServletRequest request) throws UnsupportedEncodingException {
String requestUrl = request.getParameter("request_url");
String redirectUrl = request.getParameter("redirect");
String fileName = request.getParameter("name");
if (StringUtils.isEmpty(requestUrl) || StringUtils.isEmpty(redirectUrl))
throw new BizException(ErrorCode.INVALID_ARGUMENT);
HttpClient httpClient = new DefaultHttpClient();
HttpPost httpPost = new HttpPost(requestUrl);
String auth = request.getParameter("authorization");
if (!StringUtils.isEmpty(auth))
httpPost.addHeader("Authorization", request.getParameter("authorization").toString());
MultipartEntity reqEntity = new MultipartEntity();
if (!StringUtils.isEmpty(request.getParameter("path"))) {
StringBody pathBody = new StringBody(request.getParameter("path"));
reqEntity.addPart("path", pathBody);
}
if (!StringUtils.isEmpty(request.getParameter("scope"))) {
StringBody scopeBody = new StringBody(request.getParameter("scope"));
reqEntity.addPart("scope", scopeBody);
}
if (!StringUtils.isEmpty(request.getParameter("expireDays"))) {
StringBody expireDaysBody = new StringBody(request.getParameter("expireDays"));
reqEntity.addPart("expireDays", expireDaysBody);
}
if (!StringUtils.isEmpty(fileName)) {
StringBody nameBody = new StringBody(fileName);
reqEntity.addPart("name", nameBody);
}
MultipartHttpServletRequest multipartHttpServletRequest = (MultipartHttpServletRequest) request;
MultiValueMap<String, MultipartFile> multiValueMap = multipartHttpServletRequest.getMultiFileMap();
//todo:如今临时写死,不去遍历map
if(!(multiValueMap.containsKey(CS_FILE_KEY) || multiValueMap.containsKey(UC_FILE_KEY)))
throw new BizException(ErrorCode.INVALID_ARGUMENT);
String fileKey = multiValueMap.containsKey(CS_FILE_KEY) ? CS_FILE_KEY : UC_FILE_KEY;
MultipartFile multipartFile = multipartHttpServletRequest.getFile(fileKey); // 获得文件数据
if (!multipartFile.isEmpty()) {
CommonsMultipartFile commonsMultipartFile = (CommonsMultipartFile) multipartFile;
DiskFileItem diskFileItem = (DiskFileItem) commonsMultipartFile.getFileItem();
String filePath = diskFileItem.getStoreLocation().getPath().toString();
File file = null;
try {
//推断目次是不是已存在,假如filename不为空,将其带入建立文件(实在复原文件范例,否则是.tmp临时文件)
if (StringUtils.isEmpty(fileName)) {
file = new File(filePath);
} else {
file = new File(filePath, fileName);
}
if (!file.exists()) {
file.mkdirs();
}
//保留文件
multipartFile.transferTo(file);
FileBody bin = new FileBody(file);
reqEntity.addPart(fileKey, bin);
httpPost.setEntity(reqEntity);
HttpHeaders responseHeader = new HttpHeaders();
HttpResponse httpResponse = null;
try {
httpResponse = httpClient.execute(httpPost);
} catch (Exception e) {
LOG.error("代办文件上传失利,要求地点:{},要求内容:{}", requestUrl, null, e);
JSONObject failedJson = new JSONObject();
failedJson.put("result", "FAILURE");
failedJson.put("data", e.toString());
URI uri = URI.create(redirectUrl + e.toString());
responseHeader.setLocation(uri);
return new ResponseEntity(responseHeader, HttpStatus.MOVED_TEMPORARILY);
}
LOG.info("状况码:" + httpResponse.getStatusLine().getStatusCode());
org.apache.http.HttpEntity httpEntity = httpResponse.getEntity();
//推断要求是不是胜利
String responseBody = "";
String isSuccess = "SUCCESS";
if (httpResponse.getStatusLine().getStatusCode() >= HttpStatus.OK.value() && httpResponse.getStatusLine().getStatusCode() < HttpStatus.BAD_REQUEST.value()) {
if (null != httpEntity) {
// System.out.println("相应内容:" + EntityUtils.toString(httpEntity, ContentType.getOrDefault(httpEntity).getCharset()));
responseBody = EntityUtils.toString(httpEntity, ContentType.getOrDefault(httpEntity).getCharset());
//处于平安斟酌,封闭数据流
EntityUtils.consume(httpEntity);
}
} else {
//上传失利(非2XX)
isSuccess = "FAILURE";
}
JSONObject ResJson = new JSONObject();
ResJson.put("result", isSuccess);
ResJson.put("data", responseBody);
URI uri = URI.create(redirectUrl + URLEncoder.encode(ResJson.toString(), "UTF-8"));
responseHeader.setLocation(uri);
return new ResponseEntity(responseHeader, HttpStatus.MOVED_TEMPORARILY);
} catch (IOException e) {
throw new BizException(ErrorCode.INTERNAL_SERVER_ERROR, e);
} finally {
if (file != null) {
file.delete();
}
}
}else {
throw new BizException(HttpStatus.BAD_REQUEST, "PORTAL-APP/INVALID_ARGUMENT", "上传文件为空");
}
}
在转发文件的时刻,我们做了一层转存,缘由在于,我们测试一个效劳器的时刻,我们直接运用一个缓存的数据,去写到FormData
中,那里效劳器吸收到的文件对象居然是空的,因而我们才做了一层缓存,用一个实在存在的文件去做。
—end—