一, 什么是同源策略及其限制
概念: 同源策略限制从一个源加载的文档或脚本如何与来自另一个源的资源进行交互. 这是一个用于隔离潜在恶意文件的关键安全机制.
什么是源: 协议, 域名与端口. 这三者任何一个不一样的话, 就算是跨域.
什么是限制: 不是一个源的文档, 没有权限去操作另一个源的文档.
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 访问来通信.
postMessage :
postMessage 是 HTML5
XMLHttpRequest Level 2
中的 API, 且是为数不多可以跨域操作的 window 属性之一, 它可用于解决以下方面的问题
页面和其打开的新窗口的数据传递
多窗口之间消息传递
页面与嵌套的 iframe 消息传递
上面三个场景的跨域数据传递
在这里你需要通过集成管理工具配置两个不同的域, fjw.me 下的 b 页面, demo.me 下面的 a 页面.(如下图)
image
//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>"
结果如图:
image
image
WebSocket:
WebSocket protocol 是 HTML5 一种新的协议. 它实现了浏览器与服务器全双工通信, 同时允许跨域通讯, 是 server push 技术的一种很好的实现.
原生 WebSocket API 使用起来不太方便, 我们使用 Socket.io, 它很好地封装了 webSocket 接口, 提供了更简单, 灵活的接口, 也对不支持 webSocket 的浏览器提供了向下兼容.
例子如下, 本例子是参考官方的一个简易聊天室, 地址是 http://socket.io/get-started/chat/ , 目录结构如下图所示.
image
// 服务器的后台 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>"
具体效果如下图所示, 可以在两个浏览器之间通信.
image
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).
文章参考
前端常见跨域解决方案 (全)
Ajax 是什么? 如何创建一个 Ajax?
Ajax 知识体系大梳理
跨域资源共享 CORS 详解
来源: http://www.jianshu.com/p/3de0ac01aa75