跨域-解决方案

文章目录

具体概念如下:只要协议、域名、端口有任何一个不同,都被当作是不同的域。

凡是拥有 “src” 这个属性的标签都有跨域的能力

跨域

浏览器请求(js,css,图片,form 表单提交)和 Ajax 请求都携带 Cookie 信息。但浏览器规定,静态资源请求和提交表单不受同源政策的限制,Ajax 请求受同源策略限制。

如果是协议和端口造成的跨域问题。则前台无法解决。 同源策略具体分为以下几类:

  • 不同域名
  • 相同域名不同端口号,如https://www.oschina.net:8000https://www.oschina.net:8001
  • 同一个域名不同协议,如http://www.oschina.net/https://www.oschina.net/
  • 域名和域名对应的的 IP,如http://b.qq.com/http://10.198.7.85
  • 主域和子域,如http://www.oschina.net/https://test.oschina.net
  • 子域和子域,如https://test1.oschina.nethttps://test2.oschina.net 以上情况只要出现了,那么就会产生跨域问题。那么如果解决跨域问题呢,下面的小节会总结一些解决跨域常用的方法。

JSONP 带 callback 的 json

JSONP:只支持 GET,不支持 POST 请求代理

原理:浏览器只对 XHR(XMLHttpRequest) 请求有同源限制,二队 script 标签的 src 属性、link 的 ref 和 img 的 src 属性没有这种限制,利用这个就可以解决跨域请求问题

有个通俗易懂的解释 -JSONP(JSON with Padding )是数据格式 JSON 的一种 “ 使用模式 ”,可以让网页从别的网域要数据。利用<script>标签没有跨域限制,来达到与第 3 方通讯的目的。

** jsonp 的客户端具体实现:**

  1. 远程服务器 remoteserver.com 根目录下有个 remote.js 文件代码如下:
1
alert('我是远程文件')
  1. 本地服务器 localserver.com 下有个 jsonp.html 页面代码如下:
1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<script type="text/javascript" src="http://remoteserver.com/remote.js"></script>
</head>
<body>

</body>
</html>

页面将会弹出一个提示窗体,显示跨域调用成功。

  1. 现在我们在 jsonp.html 页面定义一个函数,然后在远程 remote.js 中传入数据进行调用。

jsonp.html 页面代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<script type="text/javascript">
var localHandler = function(data){
alert('我是本地函数,可以被跨域的remote.js文件调用,远程js带来的数据是:' + data.result);
};
</script>
<script type="text/javascript" src="http://remoteserver.com/remote.js"></script>
</head>
<body>

</body>
</html>
  1. remote.js 文件代码如下:
1
localHandler({ result: '我是远程js带来的数据' })

要注意的是他支持 GET 这一种 HTTP 请求类型

跨域资源共享(CORS-Cross Origin Resource Sharing )

跨域资源共享( CORS )机制允许 Web 应用服务器进行跨域访问控制,从而使跨域数据传输得以安全进行。

CORS 除了 GET 要求方法以外也支持其他的 HTTP 要求。浏览器 CORS 请求分成两种:

  1. 简单请求
  2. 协商模型 / 预检请求(Preflighted Request ),即非简单请求如何区分请求具体属于哪一种呢,下面我总结了几点:
    1. 请求方式:
    • GET
    • HEAD
    • POST
    1. HTTP 的头信息子段
    • Accept
    • Accept-Language
    • Content-Language
    • Last-Event-ID
    • Content-Type:只限于三个值 application/x-www-form-urlencoded、multipart/form-data 、 text/plain,其中 ‘text/plain’ 默认支持,其他两种则需要预检请求和服务器协商。

满足以上两大点的即为简单请求,否则为非简单请求。

假如站点 http://foo.example 的网页应用想要访问 http://bar.other 的资源。http://foo.example 的网页中可能包含类似于下面的 JavaScript 代码:

1
2
3
4
5
6
7
8
9
10
var invocation = new XMLHttpRequest()
var url = 'http://bar.other/resources/public-data/'

function callOtherDomain() {
if (invocation) {
invocation.open('GET', url, true)
invocation.onreadystatechange = handler
invocation.send()
}
}

document.domain+iframe (适用于主域名相同的情况)

比如,有一个页面,它的地址是http://www.damonare.cn/a.html , 在这个页面里面有一个 iframe,它的 src 是http://damonare.cn/b.html, 很显然,这个页面与它里面的 iframe 框架是不同域的,所以我们是无法通过在页面中书写 js 代码来获取 iframe 中的东西的:

1
2
3
4
5
6
7
8
9
<script type="text/javascript">
function test(){
var iframe = document.getElementById('ifame');
var win = iframe.contentWindow;//可以获取到iframe里的window对象,但该window对象的属性和方法几乎是不可用的
var doc = win.document;//这里获取不到iframe里的document对象
var name = win.name;//这里同样获取不到window对象的name属性
}
</script>
<iframe id = "iframe" src="http://damonare.cn/b.html" onload = "test()"><\/iframe>

这时只要把http://www.damonare.cn/a.htmlhttp://damonare.cn/b.html这两个页面的 document.domain 都设成相同的域名就可以了。但要注意的是,document.domain 的设置是有限制的,我们只能把 document.domain 设置成自身或更高一级的父域,且主域必须相同。

  • 在页面http://www.damonare.cn/a.html 中设置 document.domain:
1
2
3
4
5
6
7
<iframe id = "iframe" src="http://damonare.cn/b.html" onload = "test()"></iframe>
<script type="text/javascript">
document.domain = 'damonare.cn';//设置成主域
function test(){
alert(document.getElementById('iframe').contentWindow);//contentWindow 可取得子窗口的 window 对象
}
</script>
  • 在页面http://damonare.cn/b.html 中也设置 document.domain:
1
2
3
<script type="text/javascript">
document.domain = 'damonare.cn' //在iframe载入这个页面也设置document.domain,使之与主页面的document.domain相同
</script>

修改 document.domain 的方法只适用于不同子域的框架间的交互。

通过 location.hash 跨域

此方法的原理就是改变 URL 的 hash 部分来进行双向通信。每个 window 通过改变其他 window 的 location 来发送消息(由于两个页面不在同一个域下 IE、Chrome 不允许修改 parent.location.hash 的值,所以要借助于父窗口域名下的一个代理 iframe),并通过监听自己的 URL 的变化来接收消息。这个方式的通信会造成一些不必要的浏览器历史记录,而且有些浏览器不支持 onhashchange 事件,需要轮询来获知 URL 的改变,最后,这样做也存在缺点,诸如数据直接暴露在了 url 中,数据容量和类型都有限等。

通过 HTML5 的 postMessage 方法跨域

这个功能主要包括接受信息的 ”message” 事件和发送消息的 ”postMessage” 方法。比如http://damonare.cn域的 A 页面通过 iframe 嵌入了一个http://google.com域的 B 页面,可以通过以下方法实现 A 和 B 的通信

A 页面通过 postMessage 方法发送消息:

1
2
3
4
5
window.onload = function () {
var ifr = document.getElementById('ifr')
var targetOrigin = 'http://www.google.com'
ifr.contentWindow.postMessage('hello world!', targetOrigin)
}

postMessage 的使用方法

otherWindow.postMessage(message, targetOrigin);

  • otherWindow: 指目标窗口,也就是给哪个 window 发消息,是 window.frames 属性的成员或者由 window.open 方法创建的窗口
  • message: 是要发送的消息,类型为 String、Object (IE8 、 9 不支持 )
  • targetOrigin: 是限定消息接收范围,不限制请使用 ‘*

B 页面通过 message 事件监听并接受消息 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var onmessage = function (event) {
var data = event.data //消息
var origin = event.origin //消息来源地址
var source = event.source //源Window对象
if (origin == 'http://www.baidu.com') {
console.log(data) //hello world!
}
}
if (typeof window.addEventListener != 'undefined') {
window.addEventListener('message', onmessage, false)
} else if (typeof window.attachEvent != 'undefined') {
//for ie
window.attachEvent('onmessage', onmessage)
}

通过 window.name 跨域

在一个窗口生命周期内,窗口载入的所有的页面都是共享一个 window.name 的,每一个页面对 window.name 都有读写的权限,window.name 是持久的存在于一个窗口载入的所有页面,并不会因为新的页面的载入而被重置。

下面为 a.html 中代码

1
2
3
4
window.name = '我是页面a中设置的值'
setInterval(function () {
window.location = 'b.html'
}, 2000) //两秒后把一个新页面b.html载入到当前的window中

b.html 中的代码

1
<script>console.log(window.name);//读取window.name的值</script>

服务器端设置 http header

这是需要在服务器端设置的,作为前端工程师我们不用详细掌握,但是要知道有这么个解决方案。而且,现在推崇的跨域解决方案是这一种,比 JSONP 简单许多。

1
2
3
4
5
6
7
8
9
response.setHeader('Access-Control-Allow-Origin', 'http://m.juejin.com/') // 第二个参数填写允许跨域的域名称,不建议直接写 "*"
response.setHeader('Access-Control-Allow-Headers', 'X-Requested-With')
response.setHeader(
'Access-Control-Allow-Methods',
'PUT,POST,GET,DELETE,OPTIONS'
)

// 接收跨域的cookie
response.setHeader('Access-Control-Allow-Credentials', 'true')

XMLHttpRequest Level 2 使用指南

XMLHttpRequest 是一个浏览器接口,是的 Javascript 可以进行 HTTP(S)通信。

老版本的 XMLHttpRequest 对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//首先 ,新建一个XMLHttpRequest 的实例。
var xhr = new XMLHttpRequest()

//然后向主机发送一个HTTP请求。
xhr.open('GET', 'example.php')
xhr.send()

//接着,就等待远程主机做出回应。这时需要监控XMLHttpRequest对象的状态变化,指定回调函数。

xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && xhr.status == 200) {
alert(xhr.responseText)
} else {
alert(xhr.statusText)
}
}

老版本的 XMLHttpRequest 的缺点

  • 只支持文本数据的传送,无法用来读取和上传二进制文件。
  • 传送和接收数据时,没有进度信息,只能提示有没有完成。
  • 受到”同源限制”,只能向同一域名的服务器请求数据。

新版本的功能

  • 可以设置 HTTP 请求发的时限。
  • 可以使用 FormData 对象管理表单数据。
  • 可以上传文件。
  • 可以请求不同域名下的数据。(跨域请求)
  • 可以获取服务器端的二进制数据。
  • 可以获的数据传输的进度信息。
分享到:
network