0%

跨域解决方案

同源策略

同源要求

  • 协议相同
  • 域名相同
  • 端口相同
1
2
3
4
http://www.example.com/dir2/other.html:同源
http://example.com/dir/other.html:不同源(域名不同)
http://v2.www.example.com/dir/other.html:不同源(域名不同)
http://www.example.com:81/dir/other.html:不同源(端口不同)

限制范围

  • cookie、localstorage和 indexDB无法读取
  • DOM 无法获得
  • Ajax 请求不能发送

1.JSONP

JSONP(JSON with Padding) 是一种跨域请求方式。主要原理是利用了script标签可以跨域请求的特性,由其 src 属性发送请求到服务器,服务器返回 JavaScript代码,浏览器接受响应,然后就直接执行了,这和通过 script 标签引用外部文件的原理是一样的。

JSONP由两部分组成:回调函数数据,回调函数一般是在浏览器控制,作为参数发往服务器端(当然,你也可以固定回调函数的名字,但客户端和服务器端的名称一定要一致)。当服务器响应时,服务器端就会把该函数和数据拼成字符串返回。

JSONP的请求过程:

  • 请求阶段:浏览器创建一个 script 标签,并给其src 赋值(类似 http://example.com/api/?callback=jsonpCallback)。
  • 发送请求:当给scriptsrc赋值时,浏览器就会发起一个请求。
  • 数据响应:服务端将要返回的数据作为参数和函数名称拼接在一起(格式类似”jsonpCallback({name: 'abc'})”)返回。当浏览器接收到了响应数据,由于发起请求的是 script,所以相当于直接调用 jsonpCallback 方法,并且传入了一个参数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// foo 函数将会被调用 传入后台返回的数据
function foo(data) {
console.log('通过jsonp获取后台数据:', data);
}
/**
* 通过手动创建一个 script 标签发送一个 get 请求
* 并利用浏览器对 <script> 不进行跨域限制的特性绕过跨域问题
*/
(function jsonp() {
let head = document.getElementsByTagName('head')[0]; // 获取head元素 把js放里面
let js = document.createElement('script');
js.src = 'http://domain:port/testJSONP?a=1&b=2&callback=foo'; // 设置请求地址
head.appendChild(js); // 这一步会发送请求
})();

// 后台代码
// 因为是通过 script 标签调用的 后台返回的相当于一个 js 文件
// 根据前端传入的 callback 的函数名直接调用该函数
// 返回的是 'foo(3)'
function testJSONP(callback, a, b) {
return `${callback}(${a + b})`;
}

允许用户传递一个callback参数给服务端,然后服务端返回数据时会将这个callback参数作为函数名来包裹住JSON数据,这样客户端就可以随意定制自己的函数来自动处理返回数据了。

使用注意

基于JSONP的实现原理,所以JSONP只能是“GET”请求,不能进行较为复杂的POST和其它请求,所以遇到那种情况,就得参考下面的CORS解决跨域了(所以如今它也基本被淘汰了)

所以JSONP的原理就是:

  • 创建一个script标签,这个script标签的src就是请求的地址;
  • 这个script标签插入到DOM中,浏览器就根据src地址访问服务器资源
  • 返回的资源是一个文本,但是因为是在script标签中,浏览器会执行它
  • 而这个文本恰好是函数调用的形式,即函数名(数据),浏览器会把它当作JS代码来执行即调用这个函数
  • 只要提前约定好这个函数名,并且这个函数存在于window对象中,就可以把数据传递给处理函数。

2.Hash

场景:当前页面 A 通过iframe或frame嵌入了跨域的页面 B

1
2
3
4
5
6
7
// 在A中伪代码如下:
var B = document.getElementsByTagName('iframe');
B.src = B.src + '#' + 'data';
// 在B中的伪代码如下
window.onhashchange = function () {
var data = window.location.hash;
};

在A页面中改变B的url中的hash值,B不会刷新,但是B可以用过window.onhashchange事件监听到hash变化。

3.postMessage

HTML5

1
2
3
4
5
6
7
8
// 窗口A(http:A.com)向跨域的窗口B(http:B.com)发送信息
Bwindow.postMessage('data', 'http://B.com');
// 在窗口B中监听
window.addEventListener('message', function (event) {
console.log(event.origin); // http://A.com
console.log(event.source); // A 对象window引用
console.log(event.data); // 数据
}, false);

Bwindow其他窗口的一个引用,比如iframe的contentWindow属性

通过窗口的origin属性来指定哪些窗口能接收到消息事件。

4.WebSocket跨域通信

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var ws = new WebSocket('wss://echo.websocket.org');

ws.onopen = function (evt) {
console.log('Connection open ...');
ws.send('Hello WebSockets!');
};

ws.onmessage = function (evt) {
console.log('Received Message: ', evt.data);
ws.close();
};

ws.onclose = function (evt) {
console.log('Connection closed.');
};

5.document.domain

浏览器有一个同源策略,其限制之一是不能通过ajax的方法去请求不同源中的文档。第二个限制是浏览器中不同域的框架之间是不能进行js的交互操作的。不同的框架之间是可以获取window对象的,但却无法获取相应的属性和方法。

这个时候,document.domain就可以派上用场了,我们只要把http://www.zj.cn/1.htmlzj.cn/2.html 这两个页面的document.domain都设成相同的域名就可以了。但要注意的是,document.domain的设置是有限制的,我们只能把document.domain设置成自身或更高一级的父域,且主域必须相同。

修改document.domain的方法只适用于不同子域的框架间的交互。

6.CORS

CORS是一个W3C标准,全称是”跨域资源共享”(Cross-origin resource sharing)。 它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。

CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。IE8+:IE8/9需要使用XDomainRequest对象来支持CORS。

浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。 因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。

两种请求

  1. 简单请求

    • 请求方式为HEAD、POST或者GET
    • http头信息不超出以下字段:Accept、Accept-Language、Content-Language、Last-Event-ID、 Content-Type(限于三个值:application/x-www-form-urlencoded、multipart/form-data、text/plain)
  2. 非简单请求

    需要通过预请求验证后然后才能发送

简单请求

在头信息之中,增加一个Origin字段。

Origin字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。

如果Origin指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段。

  • Access-Control-Allow-Origin :该字段是必须的。它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求
  • Access-Control-Allow-Credentials: 该字段可选。它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器不要浏览器发送Cookie,删除该字段即可。
  • Access-Control-Expose-Headers:该字段可选。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。

如果要把cookie发送到服务器,一方面要服务器同意,指定Access-Control-Allow-Credentials字段,另一方面,开发者必须在AJAX请求中打开withCredentials属性。

非简单请求

非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json。

非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为”预检”请求(preflight)。

浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。

1
2
3
4
5
var url = 'http://api.alice.com/cors';
var xhr = new XMLHttpRequest();
xhr.open('PUT', url, true);
xhr.setRequestHeader('X-Custom-Header', 'value');
xhr.send();

浏览器发现,这是一个非简单请求,就自动发出一个”预检”请求,要求服务器确认可以这样请求。下面是这个”预检”请求的HTTP头信息。

1
2
3
4
5
6
7
8
OPTIONS /cors HTTP/1.1
Origin: http://api.bob.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

“预检”请求用的请求方法是OPTIONS,表示这个请求是用来询问的。头信息里面,关键字段是Origin,表示请求来自哪个源。

除了Origin字段,”预检”请求的头信息包括两个特殊字段。

  • Access-Control-Request-Method:该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法,上例是PUT。
  • Access-Control-Request-Headers:该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段,上例是X-Custom-Header

服务器收到”预检”请求以后,检查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以后,确认允许跨源请求,就可以做出回应。

1
2
3
4
5
6
7
8
9
10
11
12
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
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
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

一旦服务器通过了”预检”请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段。

Koa-parser

  • URL编码 application/x-www-form-urlencoded
  • JSON编码 application/json

URL编码方式适合处理简单的键值对数据,并且很多框架的Ajax中的Content-Type默认值都是它,但是对于复杂的嵌套对象就不太好处理了,这时就需要JSON编码方式大显身手了。通过客户端JSON.stringify编码和服务端JSON.parse解码。除了上述两种字符串编码方式,koa-bodyparser还支持不采用任何字符串编码方式的普通字符串。

  三种字符串编码的处理方式由【co-body】模块提供,koa-bodyparser中通过判断当前Content-Type类型,调用不同的处理方式,将获取到的结果挂载在ctx.request.body。

  1. 如果需要支持 cookies

    • Access-Control-Allow-Origin 不能设置为 *
    • 并且 Access-Control-Allow-Credentials 需要设置为 true
    • (注意前端请求需要设置 withCredentials = true)
  2. 当 method = OPTIONS 时, 属于预检(复杂请求), 当为预检时, 可以直接返回空响应体, 对应的 http 状态码为 204

  3. 通过 Access-Control-Max-Age 可以设置预检结果的缓存, 单位(秒)

  4. 通过 Access-Control-Allow-Headers 设置需要支持的跨域请求头

  5. 通过 Access-Control-Allow-Methods 设置需要支持的跨域请求方法

-------------本文结束感谢您的阅读-------------