什么是跨域

不同源的AJAX就是跨域

协议,域名 ,端口相同就是同源

问题演示

chrome跨域插件.gif

浏览器向 web 服务器发起 http 请求时 ,如果同时满足以下三个条件时,就会出现跨域问题,从而导致 ajax 请求失败:

  • 你的浏览器多管闲事了。跨域问题出现的基本原因是浏览器出于安全性的考虑——同源策略:ajax 请求必须是同源,封杀了你跨域请求。可以安装一个浏览器插件allow-control-allow-origin来测试一下。 如果使用 postman 软件(它不是浏览器)来发请求,就不会有这个问题了。

  • 你的请求是 xhr 请求。就是常说的 ajax 请求。浏览器请求图片资源,js 文件,css 文件是可以跨域的(不是 ajax 请求)

  • 发出请求不符合同源策略要求。

    • 同源是指:协议相同域名相同端口相同。即发 ajax 请求的所在的页面 与 所请求的接口的 url 必须是同源的。

      以下就是不同源的:

      http://127.0.0.1:5500/message_front/index.html 请求http://localhost:8080/msg

    • 在前后端分离开发的场景下,前端的页面和后端的服务经常是分开部署的,所以跨域访问的情况是比较常见的。

      什么是跨域.png

    注意,错误是发生在浏览器端的。请求是可以正常从浏览器发到服务器端,服务器也可以处理请求,只是返回到浏览器端时出错了。

实现跨域的解决方案–JSONP

JSONP 简介

JSON with Padding,是一种借助于 script 标签发送跨域请求的技巧。

原理:

  • script 的 src 标签可以请求外部的 js 文件,它是可以发跨域请求的。
  • 借助 script 标签 src 请求服务端上的接口
  • 服务端的接口返回 JavaScript 脚本,并附上要返回的数据。

它其实并不是 ajax 请求

实现步骤

让 script 标签的 src 指向一个接口

前端:让 script 标签的 src 指向一个后端接口的地址;

后端:接口的返回值是一个 js 函数调用语句

前端页面

1
2
<script src="接口地址"></script>
后果 从这个接口中返回内容会当作js代码去执行

注意:

  • script 标签中的 src 会指向一个后端接口的地址。由于 script 标签并不会导致跨域问题,所以这里的请求是可以正常发送和接收的。
  • 与我们之前理解的 src 指向某个具体的.js 文件不同,我们只需要确保 src 所指向的地址的返回内容是 js 代码就行了,而不必要一定是.js 文件。

后端接口

1
2
3
4
5
6
7
8
const express = require('express');
const app = express();
app.get('/gettime', (req, res) => {
res.end(`alert(1)`);
})
app.listen(3000, () => {
console.log('你可以通过http://localhost:3000来访问...');
});

注意:

  • 后端接口的返回值是一个特殊的字符串: 一个刻意拼写的 js 函数调用语句

传递函数名到后端

前端:让 script 标签的 src 指向一个后端接口的地址,并附加函数名;

后端:接口的返回值是一个 js 函数调用语句

目标:当请求成功时,执行前端指定的函数

前端页面

1
2
3
4
5
6
<script>
function fn() {
console.log();
}
</script>
<script src="http://localhost:3000/gettime?callback=fn"></script>

注意:

  • 在前端自己定义一个函数,把函数名传给后端
  • 使用 get 方式传参,并且参数名是 callback。这个参数名要与后端保持一致。

后端接口

1
2
3
4
5
6
7
8
9
const express = require('express');
const app = express();
app.get('/gettime', (req, res) => {
let { callback } = req.query;
res.end(`${callback}()`);
})
app.listen(3000, () => {
console.log('你可以通过http://localhost:3000来访问...');
});

注意:

  • 后端接口接收函数名,并返回一个字符串,其内容是函数调用语句

后端回传数据

前端:让 script 标签的 src 指向一个后端接口的地址,并附加函数名;

后端:接口的返回值是一个 js 函数调用语句,并附加实参;

目标:当请求成功时,执行前端指定的函数

前端页面

1
2
3
4
5
6
<script>
function dosomething(rs) {
console.log(rs);
}
</script>
<script src="http://localhost:3000/getTime?callback=dosomething"></script>

注意:

  • script 标签中的 src 会指向一个后端接口的地址。由于 script 标签并不会导致跨域问题,所以这里的请求是可以常发送的。
  • 把前端的函数名传给后端

后端接口

1
2
3
4
5
6
7
8
9
10
11
const express = require('express');
const app = express();
app.get('/gettime', (req, res) => {
let { callback } = req.query;
let data = JSON.stringfy( {a:1,b:2} )
res.end(`${callback}(${data})`);
})
app.listen(3000, () => {
console.log('你可以通过http://localhost:3000来访问...');
});

注意:

  • 接收函数名,组装一个特殊的字符串:函数调用语名
  • 把要回传的参数转成字符串,并嵌入返回值,当作函数的实参。

Jquery-封装的 JSONP 前端代码

jquery 中的 ajax 已经封装好了的 jsonp 方式,你可以直接使用。具体来说就是给 ajax 请求添加一个 dataType 属性,其值为”jsonp”。注意前后端都需要改动代码。示例如下:

1
2
3
4
5
6
7
8
$.ajax({
type: "GET",
url: "http://localhost:4000/getTime",
success: function (result) {
console.log(result);
},
dataType: "jsonp", // 必须要指定dataType为jsonp
});

express 框架中的 JSONP 后端代码

1
2
3
4
5
6
7
8
9
const express = require('express');
const app = express();
app.get('/gettime', (req, res) => {
let data = {a:1,b:2}
res.jsonp(data)
})
app.listen(3000, () => {
console.log('你可以通过http://localhost:3000来访问...');
});

完整的 JSONP 代码

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>html页面</title>
</head>
<body>
<div class="container">
<h1>jsonp</h1>
<div>需要后端接口的配合:http://localhost:3005/jsonp</div>
<pre>
//--后端测试代码如下
const express = require('express');
const app = express()

// 留言板接口 -- 获取所有数据
app.get('/jsonp', (req, res) => {
var { callback } = req.query;

res.setHeader('content-type', 'application/javascript');

res.end(callback + '({a:1,b:2})');
});

app.listen(3000,()=>{})
</pre>
</div>
<script>
function buildCallBackFunction(options, callbackFunName) {
window[callbackFunName] = function (result) {
options.success(result);
window[callbackFunName] = null;
delete window[callbackFunName];
};
}
function buildParam(options) {
var params = options.params;
if (!params) {
return "";
}
if (typeof params === "object") {
var arr = [];
for (var p in params) {
arr.push(`p=${params[p]}`);
}
return arr.join("&");
} else if (typeof params === "string") {
return params;
} else {
return "";
}
}

function buildScript(url) {
var script = document.createElement("script");
script.setAttribute("src", url);
script.onload = function () {
document.getElementsByTagName("head")[0].removeChild(script);
};
document.getElementsByTagName("head")[0].appendChild(script);
}

function json(options) {
var { url, params, success } = options;
var callbackFunName = "callback_" + Date.now();
var params = buildParam(options);
params += !params
? "callback=" + callbackFunName
: "&callback=" + callbackFunName;
url += "?" + params;
buildCallBackFunction(options, callbackFunName);
buildScript(url);
}

json({
url: "http://localhost:3005/jsonp/jsonp",
// params: 'a=1&b=2',
params: { a: 1, b: 2 },
success: function (result) {
console.log(result);
},
});
</script>
</body>
</html>

实现跨域的解决方案–CORS

CORS 是一个 W3C 标准,全称是”跨域资源共享”(Cross-origin resource sharing)。它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了 AJAX 只能同源使用的限制。CORS 需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE 浏览器不能低于 IE10(ie8 通过 XDomainRequest 能支持 CORS)。

参考文档

通过在被请求的路由中设置 header 头,可以实现跨域。

1
2
3
4
5
6
7
app.get("/time", (req, res) => {
// // 允许任意源访问,不安全
// res.setHeader('Access-Control-Allow-Origin', '*')
// 允许指定源访问
res.setHeader("Access-Control-Allow-Origin", "http://www.xxx.com");
res.send(Date.now().toString());
});
  • 这种方案无需客户端作出任何变化(客户端不用改代码),就当跨域问题不存在一样。
  • 服务端响应的时候添加一个 Access-Control-Allow-Origin 的响应头
  • 如果 ajax 请求中还附加了 cookie,则还需要设置一句:res.setHeader('Access-Control-Allow-Credentials', 'true');

自行下载使用 npm cors https://www.npmjs.com/package/cors

jsonp 和 CORS 的对比

jsonp:

  • 不是 ajax

  • 只能使用get方式传参

  • 兼容性好

cors:

  • 就是 ajax

  • 支持各种方式的请求(post,get….)

  • 浏览器的支持不好