跨域是指不一样协议、域名、端口下访问js脚本。而当遇到跨域时,因为浏览器中同源策略的安全限制,致使不能正常执行,报相似如下的错误:javascript
同源策略是指限制了脚本与不一样源的资源交互,而当中的源是以协议、域名和端口区分,如下状况为不一样源:html
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:不一样源(端口不一样)
复制代码
(1) Cookie、LocalStorage 和 IndexDB 没法读取。
(2) DOM 没法得到。
(3) AJAX 请求不能发送。
复制代码
这里只总结关于AJAX方面的跨域问题。同源政策规定,AJAX请求只能发给同源的网址,不然就报错。而解决方案通常有JSONP
、CORS
和WebSocket
。前端
JSONP是服务器与客户端跨源通讯的经常使用方法,这里咱们写一个例子去探究JSONP
的原理。java
首先,网页经过添加一个<script>
元素,向服务器请求JSON数据,这种作法不受同源政策限制。jquery
render.js:
const express = require('express');
const app = express();
app.get('/jsonp', (req, res) => {
const viewPath = `${__dirname}/jsonp.html`;
res.setHeader('Content-Type', 'text/html');
res.sendFile(viewPath);
});
app.listen(8081);
console.log(`listen 8081....`);
复制代码
jsonp.html:
<html>
<body>
<h1>jsonp test</h1>
</body>
<script>
function addScriptsTag (src) {
var script = document.createElement('script');
script.setAttribute('type', 'text/javascript');
script.src = src;
document.body.appendChild(script);
}
window.onload = function () {
console.log('onload');
addScriptsTag('http://127.0.0.1:8080/ip?callback=foo');
}
function foo (data) {
console.log(`Your public IP address is:${data.ip}`);
}
</script>
</html>
复制代码
服务器收到请求后,将数据放在一个指定名字的回调函数里传回来。ios
const express = require('express');
const app = express();
app.get('/ip', (req, res) => {
const callback = req.query.callback;
res.send(`/**/ typeof ${callback} === 'function' && ${callback} ({"ip":"127.0.0.2"});`);
// res.jsonp({ ip: '127.0.0.1' });// express自带jsonp更方便
});
app.listen(8080);
console.log(`listen 8080....`);
复制代码
最后前端收到返回的数据以下所示,浏览器就会执行该脚本,调用前面定义好的回调函数foo
。express
/**/ typeof foo === 'function' && foo ({"ip":"127.0.0.2"});
复制代码
能够自行封装方法或者找第三方库去实现JSONP请求,好比jquery的jsonp方法。json
CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。axios
CORS通讯与同源的AJAX通讯没有差异,代码彻底同样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感受。跨域
浏览器将CORS请求分红两类:简单请求(simple request)和非简单请求(not-so-simple request)。
只要同时知足如下两大条件,就属于简单请求。
(1) 请求方法是如下三种方法之一:
- HEAD
- GET
- POST
(2)HTTP的头信息不超出如下几种字段:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
复制代码
凡是不一样时知足上面两个条件,就属于非简单请求
输入127.0.0.1:8081/cors
,浏览器正常进行一次简单的AJAX请求(使用axios):
render.js
const express = require('express');
const app = express();
app.get('/cors', (req, res) => {
const viewPath = `${__dirname}/cros.html`;
res.setHeader('Content-Type', 'text/html');
res.sendFile(viewPath);
});
app.listen(8081);
console.log(`listen 8081....`);
复制代码
cros.html:
<html>
<body>
<h1>cors test</h1>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
axios({
method: 'post',
baseURL: 'http://127.0.0.1:8080/',
url: '/user',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},// 默认是applictaion/json,为非简单请求,因此为了测试需自定义
data: {
firstName: 'Fred',
lastName: 'Flintstone'
},
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
</script>
</body>
</html>
复制代码
服务器经过中间件形式加上头部信息:
const express = require('express');
const app = express();
app.all('*', function(req, res, next) {
res.header("Access-Control-Allow-Origin", "http://127.0.0.1:8081");
next();
});
app.post('/user', (req, res) => {
res.send('user ok');
});
app.listen(8080);
console.log(`listen 8080....`);
复制代码
这时候CORS就成功了,通讯过程的头部信息:
请求头:
POST http://127.0.0.1:8080/user HTTP/1.1
Host: 127.0.0.1:8080
Connection: keep-alive
Content-Length: 44
Accept: application/json, text/plain, */*
Origin: http://127.0.0.1:8081
...
{"firstName":"Fred","lastName":"Flintstone"}
复制代码
响应头:
HTTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Origin: http://127.0.0.1:8081
...
user ok
复制代码
其中起关键做用的是Origin
和Access-Control-Allow-Origin
,Origin
浏览器请求时会带上,指明请求的来源。而响应头的Access-Control-Allow-Origin
指明可以跨域请求资源的容许网址,多个用逗号隔开,若是写*号代表任意网址均可以。
当请求为非简单请求时,一次请求会分两次请求,分别是预检请求和正常的请求。
咱们如今把上面AJAX请求方法变为PUT
,
请求就会先发一次预检请求。请求头以下:
OPTIONS http://127.0.0.1:8080/user HTTP/1.1
Host: 127.0.0.1:8080
Connection: keep-alive
Access-Control-Request-Method: PUT
Origin: http://127.0.0.1:8081
...
复制代码
预检请求的方法为OPTIONS
,代表请求时用来询问的。Origin
依然代表请求来源。而新增的请求字段Access-Control-Request-Method
代表接下来的CORS请求会到什么方法,可否使用。
这时服务器经过设置Access-Control-Allow-Methods
来规定服务器支持的CORS请求方法:
res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE");
复制代码
正确返回后,响应头以下:
HTTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Origin: http://127.0.0.1:8081
Access-Control-Allow-Methods: PUT,POST,GET,DELETE
Allow: PUT
...
复制代码
正常请求的请求头:
PUT http://127.0.0.1:8080/user HTTP/1.1
Host: 127.0.0.1:8080
Connection: keep-alive
Content-Length: 44
Accept: application/json, text/plain, */*
Origin: http://127.0.0.1:8081
...
{"firstName":"Fred","lastName":"Flintstone"}
复制代码
回应:
HTTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Origin: http://127.0.0.1:8081
Access-Control-Allow-Methods: PUT,POST,GET,DELETE
...
user ok
复制代码
和简单请求同样,请求写到Origin
,回包携带Access-Control-Allow-Origin
客户端,XMLHttpRequest
对象开启withCredentials
属性。
JS写法:
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
复制代码
axios开启选项便可:
withCredentials: true,
复制代码
服务端赞成容许发送Cookie,经过设置头部:
res.header("Access-Control-Allow-Credentials",true);
复制代码
Access-Control-Request-Headers
:该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段,好比但愿请求时使用application/json
格式时,你就须要在服务器设置res.header('Access-Control-Allow-Headers', 'Content-Type');
来支持自定义内容格式。
Access-Control-Max-Age
: 该字段可选,用来指定本次预检请求的有效期,单位为秒
参考下面阮一峰老师的总结。
JSONP只支持GET请求方式,客户端和服务器经过第三库来书写也很方便,服务器改动也不多。CORS支持几乎全部的请求方式,服务器支持只需设置一些头部信息,而客户端支持发送Cookie须要开启一下选项(不多改动)。WebSocket是更适用于实时性、频繁的请求。因此CORS更通用,开发时也更多使用这种方式。