摘要:由于浏览器同源策略,凡是发送请求的协议域名端口三者之间任意一与当前页面地址不同即为跨域最近项目要兼容,找了一些资料,实践了一下,现在总结一下,避免以后踩坑。解决方案,微软在和下给我们提供了来进行解决跨域问题,官方的文档可以在这里看到。
由于浏览器同源策略,凡是发送请求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
JSONPJSONP的本质是动态的加载 标签,因此其只支持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代码在执行到下载的时候就中断了,并不会继续往下执行了。所以也不是很适用。如果服务器支持返回数据格式是文本格式的话,这个组件还是挺好用的。
// 基本用法如下 //选择文件之后执行上传 $("#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
result
然后自己定义一个上传的组件,我这里是使用Vue来包装成一个组件的
这个插件是依赖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; MultiValueMapmultiValueMap = 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---
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/86953.html
摘要:本文将针对使用生态开发完成的网站,以版本为基础兼容目标,实现全功能正常使用的全面兼容解决方案。这样做的目的,是逐步减少全局性方法,使得语言逐步模块化。此外,使用这个,一旦页面不处于浏览器的当前标签,就会自动停止刷新。 前言 背景情况 vue - 2.5.11 vue-cli 使用模板 webpack-simple http请求:axios Vue 官方对于 ie 浏览器版本兼容情...
摘要:例外当涉及到同源策略时,有两个主要的例外授信范围两个相互之间高度互信的域名,如公司域名,不遵守同源策略的限制。端口未将端口号加入到同源策略的组成部分之中,因此和属于同源并且不受任何限制。 原文链接:http://www.devsai.com/2016/11/24/talk-CORS/ 同源策略(same origin policy) 1995年,同源政策由 Netscape 公司引入浏...
摘要:可以说同源策略在安全中扮演着及其重要的角色。我把这个领域的东西写成了一个系列,以后还会继续完善下去安全一同源策略与跨域安全二攻击安全三攻击 之所以要将同源策略与跨域写在一起,是因为存在浏览器的同源策略,才会存在跨域问题 何为同源策略 同源策略是浏览器实现的一种安全策略,它限制了不同源之间的文档和脚本交互的权限。只有同一个源的脚本才会具有操作dom、读写cookie、session 、a...
阅读 1379·2021-11-11 16:54
阅读 9202·2021-11-02 14:44
阅读 2334·2021-10-22 09:53
阅读 3243·2019-08-30 11:18
阅读 1934·2019-08-29 13:29
阅读 1987·2019-08-27 10:58
阅读 1612·2019-08-26 11:38
阅读 3493·2019-08-26 10:31