通信

  • 什么是同源策略限制
  • 前后端如何通信
  • 如何创建Ajax
  • 跨域通信的几种方式

一. 什么是同源策略及限制

同源策略,它是由Netscape提出的一个著名的安全策略。现在所有支持JavaScript 的浏览器都会使用这个策略。所谓同源是指,域名,协议,端口相同才能加载脚本,比如当一个浏览器的两个tab页中分别打开来 百度和谷歌的页面当浏览器的百度tab页执行一个脚本的时候会检查这个脚本是属于哪个页面的,即检查是否同源,只有和百度同源的脚本才会被执。

同源策略限制从一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的关键的安全机制。

一个包括:协议域名、**端口 ** : 这三个有一个不一样就是源不一样,就是我们所说的跨域了, 如果非同源,那么在请求数据时,浏览器会在控制台中报一个异常,提示拒绝访问。

限制:不是一个源的文档没有权利去操作另一个源的文档;

主要限制在几个方面

  • Cookie、LocalStorage 和 IndexDB 无法读取 (如我在自己的站点无法读取博客园用户的cookie)
  • DOM 无法获得
  • AJAX 请求不能发送(同源下的通信方式,但是可以通过方法规避这个限制)(Ajax只适合同源的通信)

二. 前后端如何通信

同源政策规定,AJAX请求只能发给同源的网址,否则就报错。

除了架设服务器代理(浏览器请求同源服务器,再由后者请求外部服务),有三种方法规避这个限制。

  • Ajax (同源下的通信方式)
  • WebSocket (不受同源策略的限制)
  • CORS (支持跨域通信,也支持同源通信)

三. 如何创建Ajax

  • XMLHttpRequest对象的工作流程
  • 兼容性处理
  • 事件的触发条件
  • 事件的触发顺序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//第一步,创建异步XMLHttpRequest对象
if(window.XMLHttpRequest){
let xmlhttp = new XMLHttpRequest();
}else{
let xmlhttp = new ActiveXObject('Microsoft.XMLHTTP');
}
//第二步,注册回调函数
xmlhttp.onreadystatechange = callback
//第三步,配置请求信息
xmlhttp.open("GET", "backend/api",false); // 这里同步, 第三参数不写则为true(异步)
// 若是post请求下则需要配置请求头信息
// xmlHttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");

//第四步,发送请求,
xmlhttp.send(null);
//如果是post请求
//xmlHttp.send("methodName = GetAllComment&str1=str1&str2=str2");

//第五步,被执行回调函数
function callback() {
if(xmlhttp.readyState == 200){
if(xmlhttp.status == 4){
console.log(xmlhttp.responseText);
}
}
}

上面是代码,那简单总结下如下,要完整实现一个AJAX异步调用和局部刷新,通常需要以下几个步骤:

(1)创建XMLHttpRequest对象,也就是创建一个异步调用对象.

(2)创建一个新的HTTP请求,并指定该HTTP请求的方法、URL及验证信息.

(3)设置响应HTTP请求状态变化的函数.

(4)发送HTTP请求.

(5)获取异步调用返回的数据.

(6)使用JavaScript和DOM实现局部刷新.

Ajax通常用来与后端通信传输数据的 ,如XMLjson数据

四.跨域通信的几种方式

  • JSONP
  • Hash
  • postMessage
  • WebSocket
  • CORS

1.JSONP跨域

在出现postMessage、CORS之前一直用JSONP做跨域通信的;

jsonp 利用script标签的异步加载来实现的,由于script.src 不受同源策略的限制,所以可以动态的创建script标签,将要请求数据的域写在src 中参数中附带回调的方法,服务器端返回回调函数的字符串,并带参数。

假如我们已经知道修改百度url地址链接https://www.baidu.com/s?wd=你要搜索的值 中改变wd对应后的值修改需求就能获得需求搜索相应的结果。当然百度在服务器也是对给数据开放wd数据接口的才行。

如我请求 script.src="https://www.baidu.com/s?wd=要查找的名字&callback=getInfoCallback,服务器端将返回的数据是"name:root;age:22",在客户端自定义好全局getInfoCallBack()接收函数,将获得数据并解析渲染更新HTML内容。 这种是最常见的方式。

前端代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
admin<script>
var script = document.createElement('script');
script.type = 'text/javascript';

// 传参一个回调函数名给后端,方便后端返回时执行这个在前端定义的回调函数
script.src = 'http://www.domain2.com:8080/login?user=admin&callback=handleCallback';
document.head.appendChild(script);

// 回调执行函数
function handleCallback(res) {
alert(JSON.stringify(res)); // {"status": true, "user": "admin"}
}
</script>

后端相应函数:

1
callback({"status": true, "user": "admin"})

2.Hash

请看我早期文章 hash跨域

3.postMessage

postMessageHTML5 XMLHttpRequest Level 2中的API,且是为数不多可以跨域操作的window属性之一,它可用于解决以下方面的问题:

  • 页面和其打开的新窗口的数据传递
  • 多窗口之间消息传递
  • 页面与嵌套的 iframe 消息传递
  • 上面三个场景的跨域数据传递

客户端发起:

语法:otherWindow.postMessage(message, targetOrigin, [transfer]);

1
2
3
otherWindow   其他窗口的一个引用,比如iframe的contentWindow属性、执行window.open返回的窗口对象、或者是命名过或数值索引的window.frames。
message 要发送到的数据。
targetOrigin 指定哪些窗口能接收到消息事件(其值可以是字符串"*"(表示无限制)或者一个URI)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 这里是http://www.domain1.com
<iframe id="iframe" src="http://www.domain2.com/b.html" style="display:none;"></iframe>
<script>
let iframe = document.getElementById('iframe');
iframe.onload = function() {
let data = {
name: 'hello'
};
// 向domain2传送跨域数据
iframe.contentWindow.postMessage(JSON.stringify(data), 'http://www.domain2.com');
};

// 接收从domain2返回的数据
window.addEventListener('message', function(e) {
alert('data from domain2 ---> ' + e.data); // {name:"hello",number:16}
}, false);
</script>

被跨域网站监听:

message 的属性有:

1
2
data  从客户端传递过来的对象或数据。
origin 发送方窗口的地址 origin . 这个字符串由 协议、“://“、域名、“ : 端口号”拼接而成。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 这里是http://www.domain2.com
<script>
// 接收domain1的数据
window.addEventListener('message', function(e) { // 监听分发的message
alert('data from domain1 ---> ' + e.data);

var data = JSON.parse(e.data);
if (e.origin == "http://www.domain1.com:8080"){ // 通过验证来源地址
if (data) {
data.number = 16;
// 处理后再发回domain1
window.parent.postMessage(JSON.stringify(data), 'http://www.domain1.com');
}
}
}, false);
</script>

如果您不希望从其他网站接收message,请不要为message事件添加任何事件侦听器。 这是一个完全万无一失的方式来避免安全问题。

4.WebSocket

WebSocket protocol也是HTML5一种新的协议。但它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。同时也允许跨域通讯,是server push技术的一种很好的实现。

原生WebSocket API使用起来不太方便,我们可以使用封装后的使用Socket.io模块。

WebSocket是一种通信协议,使用ws://(非加密)和wss://(加密)作为协议前缀。该协议不实行同源政策,只要服务器支持,就可以通过它进行跨源通信。

下面是一个例子,浏览器发出的WebSocket请求的头信息(摘自维基百科)。

1
2
3
4
5
6
7
8
> GET /chat HTTP/1.1
> Host: server.example.com
> Upgrade: websocket
> Connection: Upgrade
> Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
> Sec-WebSocket-Protocol: chat, superchat
> Sec-WebSocket-Version: 13
> Origin: http://example.com

上面代码中,有一个字段是Origin,表示该请求的请求源(origin),即发自哪个域名。

正是因为有了Origin这个字段,所以WebSocket才没有实行同源政策。因为服务器可以根据这个字段,判断是否许可本次通信。如果该域名在白名单内,服务器就会做出如下回应。

1
2
3
4
5
 HTTP/1.1 101 Switching Protocols
> Upgrade: websocket
> Connection: Upgrade
> Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
> Sec-WebSocket-Protocol: chat

具体用法参考文章

以及阮一峰的文章

5.CORS

Ajax一个变种,fetch实现CORS通信的–新出的通信标准,可以理解为支持跨域通信的Ajax

浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。

如何使用,请参考 阮一峰文章

Ajax是不能发送跨域通信的,浏览器在识别你用Ajax发送了一个跨域请求的时候,fetch模块它会在你http头中加一个orgin,来允许跨域通信。如果不加这个头,就是一个普通的Ajax,遇到跨域通信,浏览器就会拦截了(非法的不允许请求)。

**CROS为什么就能支持跨域的这种通信? **

浏览器会拦截ajax请求,如果它觉得这个ajax请求是跨域的,它会在http请求中,加一个origin字段(websocket就是如此)。

6.nginx配置解决iconfont跨域