本文主要讲述跨域的定义、什么样的情况会造成跨域、跨域的解决方法
一:要解决跨域的问题,需先了解以下几个概念
同源:是指相同的协议、域名、端口,三者都相同才属于同源。
解析 url = http://www.funwall.cn/
http:协议
www:服务器名(子域名)
funwall.cn:域名(主域名)
www.funwall.cn:网站名,服务器名 + 域名
同源策略:浏览器出于安全考虑,在全局层面禁止了页面加载或执行与自身来源不同域的任何脚本,站外其他来源的脚本页面同页面的交互则被严格限制
跨域:由于浏览器同源策略,凡是发送请求的url的协议、域名、端口三者之间任意一个与当前页面地址不同则视为跨域。 (简单的讲法:不同域之间相互请求资源)。
跨域资源共享(CORS):是一个解决跨域问题的好办法,从而可以使用XHR从不同的源加载数据和资源。
二:解决跨域的几种方法:可以从前端、服务器来解决
A、jsonp跨域
B、document.domain + iframe
C、window.name + iframe
D、location.hash + ifram
E、跨域资源共享(CORS)
F、WebSocket协议跨域
G、HTML5的postMessage跨域
H、nginx代理跨域
I、nodejs中间件代理跨域
三:注意
A、如果是协议和端口造成的跨域问题,那么前台是无能为力的
B、在跨域问题上,域仅仅是通过“URL的首部”来识别,而不会去判断相同的IP地址对应着两个
域或两个域是否在同一个IP地址上。
(URL的首部:window.location.protocol + window.location.domain )
(1)JSONP—解决主流浏览器get请求的跨域数据访问问题
JSONP的定义:
A、JSON with padding(填充式JSOPN,参数式JSON),看起来和JSON差不多,只不过是被包
含在函数调用中的 JSON,比如callback( { ‘name’:’jiang’ } );
B、JSONP是有效的js代码,在请求完成后,即在JSONP响应加载到页面中以后,就会立即执行
JSONP的组成:回调函数+数据,回调函数是当响应到来时,应该在页面中调用的函数。
数据是传入回调函数中的JSON数据。
JSONP的使用:通过动态<script>元素来使用,使用时可以为<script>的src属性指定一个
跨域的URL,<script>和<img>都有能力不受限制的从其他域加载资源。
典型的JSONP请求:http://freegeoip.net/json/?callback=handleResponse
JSONP的基本思想:前端和后台约定好一个函数名,当前端向服务端发送请求时,服务端返回
一段javascript,这个 javascript调用约定好的函数,并且将返回的数据当
做参数传入。
优点:能够直接访问、响应文本; 支持在浏览器与服务器之间的双向通信。
缺点:
A、 JSONP是从其他域加载代码执行。其他域不安全,可能在响应中夹带恶意代码。此时只能
完全放弃JSOPN的调用,(保证其他域的安全可靠)。(安全性问题)
B、 要确定JSONP请求是否失败并不容易。H5虽然给<script>新增一个onerror事件
处理程序,但目前还没有得到 任何浏览器支持。使用计时器检测指定时间内是否
接收到了响应。
C、 只能get请求,不支持post请求。
a、利用动态的<script>的src属性实现跨域:参考链接
动态创建<script>设置src,src的值就是要跨域的链接,回调函数在src中设置
即callback=handleResponse
查询参数callback表示回调函数,handleResponse就是回调函数名
var script = document.createElement("script");
script.src = "https://api.douban.com/v2/book/search?q=javascript&count=1&callback=handleResponse";
document.body.insertBefore(script, document.body.firstChild);
在页面中返回的json,作为参数传入回调函数中,通过回调函数来操作数据
function handleResponse(response){
// 对response数据进行操作代码
}
完整示例如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JSONP实现跨域2</title>
</head>
<body>
<div id="mydiv">
<button id="btn">点击</button>
</div>
</body>
<script type="text/javascript">
function handleResponse(response){
console.log(response);
}
</script>
<script type="text/javascript">
window.onload = function() {
var oBtn = document.getElementById('btn');
oBtn.onclick = function() {
var script = document.createElement("script");
script.src = "https://api.douban.com/v2/book/search?q=javascript&count=1&callback=handleResponse";
document.body.insertBefore(script, document.body.firstChild);
};
};
</script>
</html>
b、jQuery封装JSONP
jQuery封装的$.ajax中有个dataType的属性,如果将该属性设置成dataType:"jsonp",
就能实现JSONP跨域了。
注意:虽然jQuery将JSONP封装在$.ajax中,但是其本质和$.ajax不同。
通过jQuey封装的$.ajax实现跨域代码如下:参考链接
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>jQuery实现JSONP</title>
</head>
<body>
<div id="mydiv">
<button id="btn">点击</button>
</div>
</body>
<script type="text/javascript" src="https://code.jquery.com/jquery-3.1.0.min.js"></script>
<script type="text/javascript">
$(function(){
$("#btn").click(function(){
$.ajax({
async : true,
url : "https://api.douban.com/v2/book/search", //跨域的url,需要访问的跨域链接
type : "GET", //请求方式只能是GET
dataType : "jsonp", // 返回的数据类型,设置为JSONP方式
jsonp : 'callback', //指定一个查询参数名称来覆盖默认的 jsonp 回调参数名 callback,任意设置
jsonpCallback: 'handleResponse', //设置回调函数名
data : { //请求参数
q : "javascript",
count : 1
},
success: function(response, status, xhr){
console.log('状态为:' + status + ',状态是:' + xhr.statusText);
console.log(response);
}
});
});
});
</script>
</html>
c、通过$.getJSON()来实现,只要在地址中加入字符串查询参数:callback=?即可,
这里的callback=?表示作为成功的回调函数 参考链接
$.getJSON("https://api.douban.com/v2/book/search?q=javascript&count=1&callback=?", function(data){
console.log(data);
});
(2)XHR2(HTML5) 实现跨域资源共享CORS
Html5提供的XMLHttpRequest Level2已经实现了跨域访问以及其他一些新功能。 IE10以下不支持XHR2这个标准。
CORS( Cross-Origin-resource-Sharing,跨域资源共享 ):定义了在访问跨域资源时,浏览器与服务器应该如何沟通。
CORS需要浏览器和服务器同时支持,整个CORS通信过程,都是浏览器自动完成,不需要用户参与。CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。因此,只要服务器实现了CORS接口,就可以跨源通信。
两种请求:浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)
1) 请求方法是以下三种方法之一:
HEAD
GET
POST
2) HTTP的头信息不超出以下几种字段:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
满足以上两大条件就是简单请求
1)简单请求---浏览器发现这次跨源AJAX请求是简单请求,就自动在头信息之中增加一个Origin
字段
Origin
字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。
GET /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
Origin
指定的源,不在服务器许可范围内,服务器会返回一个正常的HTTP回应。浏览器发现,这个响应头信息没有包含Access-Control-Allow-Origin
字段,就知道出错了,从而抛出一个错误,被XMLHttpRequest
的onerror
回调函数捕获。注意,这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200。
Origin
指定的域名在服务器许可范围内,响应头会多出几个头信息字段,
//必须,它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求
Access-Control-Allow-Origin: http://api.bob.com
//可选,只能为true,表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器
Access-Control-Allow-Credentials: true
//可选,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段,通过
//它可以拿到额外字段
Access-Control-Expose-Headers: FooBar
//设置MIME类型
Content-Type: text/html; charset=utf-8
2)非简单请求---会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)
浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest
请求,否则就报错
具体过程
var url = 'http://api.alice.com/cors';
var xhr = new XMLHttpRequest();
xhr.open('PUT', url, true);
xhr.setRequestHeader('X-Custom-Header', 'value');
xhr.send();
a)浏览器发现,这是一个非简单请求,就自动发出一个"预检"请求,要求服务器确认可以这样请
OPTIONS /cors HTTP/1.1 //请求方法OPTIONS表示这个请求是用来询问的
Origin: http://api.bob.com //请求来自哪个源
Access-Control-Request-Method: PUT //必须,列出浏览器的CORS请求会用到哪些HTTP方法
Access-Control-Request-Headers: X-Custom-Header //指定浏览器CORS请求会额外发送的头信息字段
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
b)预检请求的回应
服务器收到"预检"请求以后,检查了Origin
、Access-Control-Request-Method
和Access-Control-Request-Headers
字段以后,确认允许跨源请求,就可以做出回应
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://api.bob.com //必须,设置可以跨域的url,为*同意任意跨源请求
Access-Control-Allow-Methods: GET, POST, PUT //必须,服务器支持的所有跨域请求的方法
Access-Control-Allow-Headers: X-Custom-Header //如果浏览器请求包括Access-Control-Request-Headers字段,则必须,服务器支持的所有头信息字段
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain
如果服务器否定了"预检"请求,会返回一个正常的HTTP回应,但是没有任何CORS相关的头信息字段。这时,浏览器就会认定,服务器不同意预检请求,因此触发一个错误,被XMLHttpRequest
对象的onerror
回调函数捕获。控制台会打印出如下的报错信息
XMLHttpRequest cannot load http://api.alice.com.
Origin http://api.bob.com is not allowed by Access-Control-Allow-Origin.
c)一旦服务器通过了"预检"请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin
头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin
头信息字段
基本思想:使用自定义的HTTP头部,让浏览器与服务器进行沟通,从而决定请求或响应是失败还是成功
服务器:只需要设置两个服务器头
A、 header(‘Access-Control-Allow-Origin:*’);‘*’代表所有域都可以访问,也可以设置
特定的访问域名,指定授 权访问的域
B、 header(‘Access-Control-Allow-Methods:POST,GET’); //授权请求的方法
CORS和JSONP比较:
A、JSONP只能实现GET请求,而CROS能实现所有类型的HTTP请求(GET POST OPTIONS
HEAD PUT DELETE TRACE CONNECT,常用的请求GET POST)
B、使用CORS,开发者可以使用普通的XMLHttpRequest发起请求和获得数据,比起JSONP有更好的错误处理
C、JSONP的优势在于支持老式浏览器,可以向不支持CORS的网站请求数据,而绝大多数现代浏览器都已经支持CORS
(3)后端做一个代理,跟前端没有关系
在同域名的web服务器创建一个代理。
桂林服务器:www.guilin.com
深圳服务器:www.shenzhen.com
在深圳的web服务器的后台(www.shenzhen.com/proxy-guilinservice.php)
来调用桂林服务器( www.guilin.com/service.php )的服务,然后把响应结果返回给前端,这样前端调用深圳同域名的服务就 和调用桂林的服务效果相同了。
(4) document.domain + ifram 跨域
该方法只适用于不同子域的框架间的交互。
同源策略的限制:
A、不能通过ajax的方法去请求不同源中的资源
B、浏览器中,不 同域的框架之间是不能进行js交互操作
不同的框架之间是可以获取window对象,但却无法获取相应的属性和方法
比如:有一个页面的地址:http://www.a.cn/a.html,在这个页面里面包含一个iframe
,src=http://a.cn/b.html,显然他们的二级域名不同,所以无法在a.html页面书
写js代码获取iframe中的东西
<script type="text/javascript">
functiontest(){
variframe = document.getElementById('#ifame');
var win= document.contentWindow;//可以获取到iframe里的window对象,但该window对象的属性和方法几乎是不可用的
var doc= win.document;//这里获取不到iframe里的document对象
var name= win.name;//这里同样获取不到window对象的name属性
}
</script>
<iframe id = "iframe"src="http://a.cn/b.html" onload ="test()"></iframe>
解决方法:document.domain + iframe
把两个页面的document.domain设成相同的域名就可以了(注意:document.domain只能设
置成自身或更高一级的父域,且主域必须相同)
A、在页面 http://a.cn/b.html中设置document.domain
<script type="text/javascript">
document.domain=a.cn';//在iframe载入这个页面也设置
//document.domain与主页面的document.domain相同
</script>
B、在页面http://www.a.cn/a.html 中设置document.domain
<iframe id = "iframe"src="http://damonare.cn/b.html" onload ="test()"></iframe>
<scripttype="text/javascript">
document.domain = a.cn';//设置成主域
functiontest(){
alert(document.getElementById('#iframe').contentWindow.name);
//contentWindow 可取得子窗口的 window 对象
}
</script>
(5)window.name + iframe
window对象有个name属性,该属性特征:在一个窗口(window)的生命周期内,窗口载入的
所有的页面都共享一个window.name,每个页面对window.name都有读写的权利,
window,name是持久存在一个窗口载入过的所有页面中的并不会因为新页面的载入而进行重置
例如:
在任意一个页面输入:
window.name ="My window's name";
setTimeout(function(){
window.location.href = "http://a.cn/";
},1000);
进入a.cn页面后在检测window.name:
Console.log(window.name); // My window's name
结论:在一个标签里面跳转网页,window.name是不会改变的。
基于这个思想,可在某个页面设置好window.name的值,然后跳转到另外一个页面,在这个页
面可获取设置好的window,name(string类型)的值。
将window.name应用到iframe上面
A、在页面http://a.cn/index.html中内嵌一个iframe:
<iframe id="iframe"src="http://www.google.com/iframe.html"></iframe>
B、在iframe.html中设置好window.name为我们要传递的字符串
C、在index.html里面写下代码:
var iframe =document.getElementById('iframe');
var data = '';
iframe.onload = function() {
data = iframe.contentWindow.name;
};
问题:BOM报错,因为两个页面不同源,解决代码:
var iframe = document.getElementById('iframe');
var data = '';
iframe.onload = function() {
iframe.onload = function(){
data =iframe.contentWindow.name;
}
iframe.src ='about:blank';
};
或者将ifram.src设置成某个同源页面
Window.name+ iframe 和 document.domain + iframe的区别:
前者放宽了域名后缀要相同的限制,可以从任意页面获取string类型的数据。
(6)location.hash
(7)HTML5的postMessage
待续。。。