因为浏览器同源策略,凡是发送请求url的协议、域名、端口三者之间任意一与当前页面地址不一样即为跨域javascript
最近项目要兼容IE9,找了一些资料,实践了一下,如今总结一下,避免之后踩坑。html
第一次碰到这个问题,因此就是上网找找有没有什么好的解决方案。最初找到的方案是这样的,直接在IE中设置设置受信任的站点,而后容许其能够进行跨域访问,最后在jQuery
中设置开启跨域请求。oh,No!这么粗暴,好吧,这也是一个不是办法的办法,若是作的是一个小项目,用户很少,那直接写在用户手册里,让他们本身去配吧。可是,这显然不是一个好的解决方案啊,那只能继续找了。前端
ok,微软在IE8
和IE9
下给咱们提供了XDomainRequest
来进行解决跨域问题,官方的文档能够在 这里看到。固然Github
上也有开源的jQuery
插件,能够在这里找到。java
XDR
的限制:jquery
XDR
仅支持GET
与POST
这两种请求方式,虽然可使用上面提交的插件来解决前端部分只要进行简单修改代码就能够提交PUT
/HEAD
/DELETE
的请求的问题,可是其请求的发生出去依旧仍是将PUT
/HEAD
/DELETE
转化为POST
,将HEAD
转化为GET
请求。当是POST
请求的时候,请求方案会以__method=原请求
的方式结构加入到请求体的body
中。当是HEAD
请求的时候,请求方案会以__method=原请求
的方式结构加入请求url的查询参数中。如今大部分API开发都是按照RESTful
规范进行设计的,若是是本身的服务端还好,能够叫服务端的同窗添加一个拦截器作一个拦截判断,而后执行对应的方法(ps:我想过去应该是这个样子,不知道服务端的同窗会不会磨刀子)。可是若是你调用是网上的API的接口的话,那就心有余而力不足了。git
XDR不支持自定义的请求头
,所以若是你的服务端是用过header
中的自定义参数进行作身份验证的话,那也行不通了。github
请求头的Content-Type
只容许设置为text/plain
web
XDR
不容许跨协议的请求,若是你的网页是在HTTP
协议下,那么你只能请求HTTP
协议下的接口,不能访问HTTPS
下的接口。ajax
XDR
只接受HTTP/HTTPS
的请求apache
发起请求的时候,不会携带authentication
或 cookies
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,如今就来说讲这两个插件
适用于服务器返回的数据是文本格式
这份代码也很简单就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); } }); });
适用于服务器返回的数据是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---