一,什么是同源策略及其限制
概念:同源策略限制从一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的关键安全机制。
什么是源:协议、域名与端口。这三者任何一个不一样的话,就算是跨域。
什么是限制:不是一个源的文档,没有权限去操作另一个源的文档。
Cookie、LocalStorage 和 IndexDB无法读取。
Dom无法获得
Ajax请求不能发送
常见跨域场景
URL 说明 是否允许通信
http://www.domain.com/a.js
http://www.domain.com/b.js 同一域名,不同文件或路径 允许
http://www.domain.com/lab/c.js
http://www.domain.com:8000/a.js
http://www.domain.com/b.js 同一域名,不同端口 不允许
http://www.domain.com/a.js
https://www.domain.com/b.js 同一域名,不同协议 不允许
http://www.domain.com/a.js
http://192.168.4.12/b.js 域名和域名对应相同ip 不允许
http://www.domain.com/a.js
http://x.domain.com/b.js 主域相同,子域不同 不允许
http://domain.com/c.js
http://www.domain1.com/a.js
http://www.domain2.com/b.js 不同域名 不允许
二,前端端如何通信
- Ajax
- WebSocket(不受同源策略限制)
- CORS
三,如何创建ajax
- XMLHttpRequest对象的工作流程
- 兼容性处理:IE下面的兼容性处理
- 事件的触发条件:事件的触发动作
- 事件的触发顺序:每个事件的触发顺序
下面是个简单的实现ajax的例子
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>ajax</title>
</head>
<body>
<div id="myDiv"></div>
<script type="text/javascript">
function ajax(url,method){
var xmlhttp;
if (window.XMLHttpRequest){// 兼容 IE7+, Firefox, Chrome, Opera, Safari
xmlhttp=new XMLHttpRequest();
}
else{// 兼容 IE6, IE5
xmlhttp=newActiveXObject("Microsoft.XMLHTTP");
}
xmlhttp.open(method,url,true);
xmlhttp.send();
xmlhttp.onreadystatechange=function(){
if(xmlhttp.readyState==4 &&xmlhttp.status==200){
document.getElementById("myDiv").innerHTML=xmlhttp.responseText;
}
}
}
ajax(" http://www.easy-mock.com/mock/59ba1ac1e0dc663341a981b5/demo/list",'get');
</script>
</body>
</html>
注意一:xmlhttp.readyState
:一共有5中请求状态,从0 到 4 发生变化。**
0: 请求未初始化
1: 服务器连接已建立
2: 请求已接收
3: 请求处理中
4: 请求已完成,且响应已就绪
注意二:xmlhttp.status
:响应状态码。这个也是面试比较爱问的,这个必须知道4个以上,比较常见的有:
200 "OK"
403 (禁止) 服务器拒绝请求。
404 (未找到) 服务器找不到请求的网页。
408 (请求超时) 服务器等候请求时发生超时。
500 (服务器内部错误) 服务器遇到错误,无法完成请求。
注意三:xmlhttp.open
:方法open
的参数要牢记,很多面试官爱问这样的细节
method:请求的类型;GET 或 POST
url:文件在服务器上的位置
async:true(异步)或 false(同步)
注意四:post
请求一定要设置请求头的格式内容
xmlhttp.open("POST","ajax_test.html",true);
xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");
xmlhttp.send("fname=Henry&lname=Ford");
注意五:服务器响应处理
responseText 获得字符串形式的响应数据。
responseXML 获得XML 形式的响应数据。
知识介绍 : Ajax详细介绍
四,跨域通信的几种方式
-
JSONP
通常为了减轻web服务器的负载,我们把js、css,img等静态资源分离到另一台独立域名的服务器上,在html页面中再通过相应的标签从不同域名下加载静态资源,而被浏览器允许,基于此原理,我们可以通过动态创建script,再请求一个带参网址实现跨域通信。
js原生实现方式
<script>
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = 'https://api.asilu.com/geo/&callback=jsonp';//这个是获取当前经纬度的接口
document.head.appendChild(script);//创建并添加script标签到<head>下
// 回调执行函数
function jsonp(res) {
console.log(res);//打印jsonp返回的信息
}
</script>
jq的Ajax实现方式
<script type="text/javascript">
$.ajax({
url: 'https://api.asilu.com/geo/',
type: 'get',
dataType: 'jsonp', // 请求方式为jsonp
success:function(ret){
console.log(ret);
}
});
</script>
优点:它不像XMLHttpRequest对象实现的Ajax请求那样受到同源策略的限制;它的兼容性更好,在更加古老的浏览器中都可以运行,不需要XMLHttpRequest或ActiveX的支持;并且在请求完毕后可以通过调用callback的方式回传结果。
缺点:它只支持GET请求;安全问题(请求代码中可能存在安全隐患);
-
Hash
hash改变,但是其的域名不改变。可以利用iframe。例子 ==> a与b跨域相互通信,通过中间页c来实现。 三个页面,不同域之间利用iframe的location.hash传值,相同域之间直接js访问来通信。</font>
-
postMessage :
postMessage
是HTML5 XMLHttpRequest Level 2
中的API,且是为数不多可以跨域操作的window属性之一,它可用于解决以下方面的问题
页面和其打开的新窗口的数据传递
多窗口之间消息传递
页面与嵌套的iframe消息传递
上面三个场景的跨域数据传递
在这里你需要通过集成管理工具配置两个不同的域,fjw.me
下的b页面,demo.me
下面的a页面。(如下图)
//A.HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>b.html</title>
</head>
<body>
<h1>b.html</h1>
<iframe id="iframe" src="http://fjw.me/"></iframe>
<script type="text/javascript">
var iframe = document.getElementById('iframe');
iframe.onload = function() {
var data = {
name: 'aym'
};
// 向fjw.me传送跨域数据,通过postMessage发送数据
iframe.contentWindow.postMessage(JSON.stringify(data), 'http://fjw.me/index.html');
};
// 接受fjw.me返回数据
window.addEventListener('message', function(e) {
alert('data from domain2 ---> ' + e.data);
}, false);
</script>
</body>
</html>
//B.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>index</title>
</head>
<body>
<div>123456</div>
<script type="text/javascript">
// 接收demo.me的数据
window.addEventListener('message', function(e) {
alert('data from domain1 ---> ' + e.data);
var data = JSON.parse(e.data);
if (data) {
data.number = 16;
// 处理后再发回demo.me
window.parent.postMessage(JSON.stringify(data), 'http://demo.me/b.html');
}
}, false);
</script>
</body>
</html>
结果如图:
-
WebSocket:
WebSocket protocol是HTML5一种新的协议。它实现了浏览器与服务器全双工通信,同时允许跨域通讯,是server push技术的一种很好的实现。
原生WebSocket API使用起来不太方便,我们使用Socket.io,它很好地封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的浏览器提供了向下兼容。
例子如下,本例子是参考官方的一个简易聊天室,地址是http://socket.io/get-started/chat/,目录结构如下图所示。
//服务器的后台node.js
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
var port = process.env.PORT || 3000;
app.get('/', function(req, res){
res.sendFile(__dirname + '/index.html');
});
io.on('connection', function(socket){
socket.on('chat message', function(msg){
io.emit('chat message', msg);
}); //监听socket是否连接,如果连接的话,就发送数据,chat-message
socket.on('disconnect', function(){
console.log('user disconnected');
}); //监听socket是否断开,断开时执行相应的方法
});
http.listen(port, function(){
console.log('listening on *:' + port);
});
//前端客户端
<!doctype html>
<html>
<head>
<title>Socket.IO chat</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font: 13px Helvetica, Arial; }
form { background: #000; padding: 3px; position: fixed; bottom: 0; width: 100%; }
form input { border: 0; padding: 10px; width: 90%; margin-right: .5%; }
form button { width: 9%; background: rgb(130, 224, 255); border: none; padding: 10px; }
#messages { list-style-type: none; margin: 0; padding: 0; }
#messages li { padding: 5px 10px; }
#messages li:nth-child(odd) { background: #eee; }
#messages { margin-bottom: 40px }
</style>
</head>
<body>
<ul id="messages"></ul>
<form action="">
<input id="m" autocomplete="off" /><button>Send</button>
</form>
<script src="https://cdn.socket.io/socket.io-1.2.0.js"></script>
<script src="https://code.jquery.com/jquery-1.11.1.js"></script>
<script>
$(function () {
var socket = io();//建立一个socket
$('form').submit(function(){
socket.emit('chat message', $('#m').val());//发送socket到服务器。
$('#m').val('');
return false;
});
socket.on('chat message', function(msg){
$('#messages').append($('<li>').text(msg));
window.scrollTo(0, document.body.scrollHeight);
//接受服务器传回来的数据,新建一个li标签
});
});
</script>
</body>
</html>
具体效果如下图所示,可以在两个浏览器之间通信。
-
5,CORS:
普通跨域请求:只服务端设置Access-Control-Allow-Origin
即可,前端无须设置,若要带cookie请求:前后端都需要设置。参考阮一峰老师的这一篇CORS的文章
文章的地址:跨域资源共享 CORS 详解
5.1, CORS的通信过程
整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。
5,2, CORS的浏览器限制
目前,所有浏览器都支持该功能,IE浏览器不能低于IE10
5.3, 具体流程
第一步:浏览器直接发出CORS
请求。具体来说,就是在头信息之中,增加一个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
字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。
如果Origin
指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段。如下所示:
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8
意思分别是:
5.3.1,Access-Control-Allow-Origin
该字段是必须的。它的值要么是请求时Origin
字段的值,要么是一个*
,表示接受任意域名的请求。
5.3.2,Access-Control-Allow-Credentials
该字段可选。它的值是一个布尔值,表示是否允许发送Cookie
。默认情况下,Cookie
不包括在CORS
请求之中。设为true
,即表示服务器明确许可,Cookie
可以包含在请求中,一起发给服务器。这个值也只能设为true
,如果服务器不要浏览器发送Cookie
,删除该字段即可。
5.3.3,Access-Control-Expose-Headers
该字段可选。CORS
请求时,XMLHttpRequest
对象的getResponseHeader()
方法只能拿到6个基本字段:Cache-Control
、Content-Language
、Content-Type
、Expires
、Last-Modified
、Pragma
。如果想拿到其他字段,就必须在Access-Control-Expose-Headers
里面指定。上面的例子指定,getResponseHeader('FooBar')
可以返回FooBar
字段的值。
还有我们说到上面说到,CORS
请求默认不发送Cookie
和HTTP
认证信息。如果要把Cookie
发到服务器,一方面要服务器同意,指定Access-Control-Allow-Credentials
字段。
Access-Control-Allow-Credentials: true
前端也要设置:
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;//这个要开起来
5.4, 优点
CORS与JSONP相比,无疑更为先进、方便和可靠。
- JSONP只能实现GET请求,而CORS支持所有类型的HTTP请求。
- 使用CORS,开发者可以使用普通的XMLHttpRequest发起请求和获得数据,比起JSONP有更好的错误处理。
- JSONP主要被老的浏览器支持,它们往往不支持CORS,而绝大多数现代浏览器都已经支持了CORS)。