1、跨域
一个域名地址的组成
简单的理解就是:
因为javascript同源策略的限制
当协议、子域名、主域名、端口号中任意一个不相同时,都算作不同域【不同域之间相互请求资源,就算作’跨域’】
注意:【跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果
被浏览器拦截了
】 - 【之所以会跨域,是因为受到了同源策略的限制,同源策略要求源相同才能正常进行通信,即协议、域名、端口号都完全一致
】如果是协议和端口造成的跨域问题’前台’是无能为力的
在跨域问题上,
域
仅仅是通过URL的首部
来识别而不会根据域名对应的IP地址是否相同来判断。’URL的首部’可以理解为协议, 域名和端口必须匹配
2、同源策略及其限制
2.1 概念
- 同源策略限制从一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的关键的安全机制。它的存在可以
保护用户隐私信息,防止身份伪造等(读取Cookie)
2.2 同源策略限制内容
Cookie、LocalStorage、IndexedDB 等
存储性内容
DOM节点
Ajax
请求不能发送
2.3 允许跨域的三个标签
img
link
script
3、解决跨域的方法
3.1 jsonp
3.1.1 jsonp原理
- 利用
script
元素的这个开放策略
,网页可以得到从其他来源动态产生的JSON数据
【JSONP请求一定需要对方的服务器做支持才可以】
3.1.2 jsonp和ajax的对比
- JSONP和AJAX相同,都是
客户端向服务器端
发送请求,从服务器端获取数据的方式。但AJAX属于同源策略
,JSONP属于非同源策略(跨域请求)
3.1.3 jsonp的优缺点
优点:
兼容性好,可用于解决主流浏览器的跨域数据访问的问题
缺点:
仅支持get方法,具有很大的局限性
3.1.4 jsonp的流程
<script>
function fn(data) {
console.log(data);
}
/*
1.声明一个回调函数,其函数名(fn)当作参数值,要传递给跨域请求的服务器【函数的形参(data)为获取目标数据,也就是服务器返回的data数据】
2.创建script标签,将地址赋值给src,还要在这个地址中向服务器传递该函数名【可以通过问号传参 :?callback=fn】
3.服务器接收到请求后,需要进行特殊的处理:把传递进来的函数名和它需要给你的数据拼接成一个字符串 【传递进去的函数名是fn,它准备好的数据是 fn([{"name":"jianshu"}])】
4.最后服务器把准备的数据通过HTTP协议返回给客户端,客户端再调用执行之前声明的回调函数(fn),对返回的数据进行操作
*/
</script>
<script src='http://crossdomain.com/jsonServerResponse?jsonp=fn'></script>
<!--
注:
1.其中 fn 是客户端注册的回调的函数,目的获取跨域服务器上的json数据后,对数据进行在处理
2.服务器返回给客户端数据的格式为 fn({ msg:'this is json data'})
-->
3.1.5 jquery的jsonp形式
- JSONP都是
GET和异步请求
的,不存在其他的请求方式和同步请求,且jQuery默认就会给JSONP的请求清除缓存
$.ajax({
url: 'http://crossdomain.com/jsonServerResponse',
dataType: 'jsonp',
type: 'get', // -> 这个可以省略,因为jsonp只会发送get请求
jsonpCallback: "fn",// -> 自定义传递给服务器的函数名,而不是使用JQ自动生成的,可省略
jsonp: "jsonp",// -> 把传递函数名的那个形参callback变为jsonp,可省略
success: function(data) {
console.log(data);
}
});
3.2 CORS
3.2.1 CORS原理
整个
CORS通信过程
,都是浏览器自动完成
,不需要用户参与
【对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样.浏览器一旦发现AJAX请求跨源,就会自动添加
一些附加的头信息
,有时还会多出一次附加的请求,但用户不会有感觉】实现CORS通信的关键是服务器.只要服务器实现了CORS接口,就可以跨源通信
3.2.2 代码示例
//-> PHP
header(
"Access-Control-Allow-Origin:*"
);
header(
"Access-Control-Allow-Methods:POST,GET"
);
// -> NodeJs
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Headers", "Content-Type,Content-Length, Authorization, Accept,X-Requested-With");
res.setHeader("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");
res.setHeader("X-Powered-By", ' 3.2.1')
if (req.method == "OPTIONS")
return res.end();
- 在响应头上添加 Access-Control-Allow-Origin 属性,
指定同源策略的地址
。同源策略默认地址是网页的本身。只要浏览器检测到响应头带上了CORS,并且允许的源包括了本网站,那么就不会拦截请求响应
3.3 WebSocket
3.3.1 介绍
Websocket是HTML5的一个持久化的协议,它实现了浏览器与服务器的全双工通信,同时也是跨域的一种解决方案
WebSocket和HTTP都是应用层协议,都
基于 TCP 协议
.但是WebSocket
是一种双向通信协议
,在建立连接之后,WebSocket 的 server 与 client 都能主动向对方发送或接收数据
【WebSocket 在建立连接时需要借助 HTTP 协议,连接建立好了之后 client 与 server 之间的双向通信就与 HTTP 无关了】
3.3.2 使用socket.io.js
- 原生WebSocket API使用起来不太方便,我们使用Socket.io,它很好地
封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的浏览器提供了向下兼容
<!-- 前端代码 -->
<div>user input:<input type='text'/></div>
<script src='./socket.io.js'></script>
<script>
var socket = io('http://www.doamin2.com:8080');
// -> 连接成功处理
socket.on('connect', () => {
// -> 监听服务端消息
socket.on('message', (msg) => {
console.log('data from server: -->' + msg);
});
// -> 监听服务端关闭
socket.on('disconnect', () => {
console.log('Server has closed');
});
});
document.getElementsByTagName('input')[0].onblur = () => {
socket.send(this.value);
}
</script>
// -> NodeJs socket后台
var http = require('http'),
socket = require('socket.io');
var server = http.createServer((req, res) => {
res.writeHead(200, {
'Content-type': 'text/html;charset=utf8'
});
res.end();
});
server.listen(4000);
// -> 监听socket连接
socket.listen(server).on('connection', (client) => {
// -> 接收信息
client.on('message', (msg) => {
client.send('hello:' + msg);
console.log('data from client: ---->' + msg);
});
// -> 断开处理
client.on('disconnect', () => {
console.log('Client has closed');
})
})
3.4 postMessage
如果两个网页不同源,就无法拿到对方的DOM,【HTML5为了解决这个问题,引入了一个全新的API:
跨文档通信 API
(Cross-document messaging).这个API为window对象新增了一个window.postMessage
方法,允许跨窗口通信,不论这两个窗口是否同源
】postMessage方法的
第一个参数是具体的信息内容
,第二个参数是接收消息的窗口的源(origin)
,即”协议 + 域名 + 端口
“。也可以设为 *,表示不限制域名,向所有窗口发送
<!-- 发送信息页面 http://localhost:63342/index.html -->
<iframe src='http://localhost:3000/users/reg' id="frm"></iframe>
<input type='button' value='ok' onclick="run()"/>
<script>
function run () {
var frm = document.getElementById('frm');
frm.contentWindow.postMessage('Message Info','http://localhost:3000')
}
</script>
// -> 接收信息页面 http://localhost:3000/message.html
window.addEventListener("message", (e) => {
console.log(e.data);
}, false)