跨域请求方法总结

同源策略

含义

起初的含义是指,A网页设置的Cookie,B网页不能打开,除非这两个网页”同源”。所谓”同源”是指一下几个相同:

  • 协议相同(http,https,http2等)
  • 域名相同(www.example.com)
  • 端口相同(www.example.com:3000)

目的

同源策略的出现时为了保护用户信息的安全。如果用户登录了A网站,又去浏览其他网站。如果其他网站可以获取到A网站的Cookie,重要用户信息可能就会泄露。

通过XHR实现Ajax的一个主要限制,来自于跨域安全策略。这种安全策略可以防止某些恶意行为,但合理的实现跨域请求对某些应用来说也是非常必要的。

限制范围

目前,如果非同源,共有三种行为受到限制:

  • Cookie、LocalStorage 和 IndexedDB 无法获取
  • DOM 无法获得
  • AJAX 请求不能发送

规避方法

我们首先构建出一个跨域的环境方便测试:

1
2
3
4
5
6
7
8
9
10
11
12
├── client
│ ├── app.js
│ ├── package.json
│ └── public
│ └── src
│ ├── index.html
│ └── index.js
└── server
├── app.js
├── package.json
└── public
└── src
  • 通过express分别在3000和4000端口启动两个服务
  • 默认server为3000端口、client在4000端口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// /server/app.js
var express = require('express')
var path = require('path')
var app = express()
app.use('/static', express.static(path.join(__dirname, 'public')))
app.get('/', function (req, res) {
res.send('hello world');
})
app.listen(3000, function () {
console.log('server app listening on port 3000!')
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// /client/app.js
var express = require('express')
var path = require('path')
var app = express()
app.use('/static', express.static(path.join(__dirname, 'public')))
app.get('/', function (req, res) {
res.send('client')
})
app.listen(4000, function () {
console.log('client app listening on port 4000!')
})
1
2
3
4
5
6
7
8
9
10
11
12
13
<!--/client/public/src/index.html-->
<!DOCTYPE html>
<html lang="en">
<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>Document</title>
</head>
<body>
<script src="./index.js"></script>
</body>
</html>

环境搭建成功后,在4000端口上的js通过Ajax向3000端口的服务发送请求,就会触发跨域安全策略。

下面就介绍几种CORS(Cross-Origin Resource Sharing)的方法:

原生支持

Firefox 3.5+、Safari 4+、Chrome等主流浏览器都通过XMLHttpRequest对象实现了CORS的原生支持。

1
2
3
4
5
6
7
8
9
10
11
12
// /client/public/src/index.js
var xhr = new XMLHttpRequest();
xhr.onload = function() {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300 || xhr === 304) {
console.log(xhr.responseText)
} else {
console.log('fail');
}
}
}

同时需要对接口进行设置。通过设置Access-Control-Allow-Origin头可以决定来自哪些域的请求可以被实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// /server/app.js
var express = require('express')
var path = require('path')
var app = express()
app.use('/static', express.static(path.join(__dirname, 'public')))
app.get('/', function (req, res) {
+ res.header('Access-Control-Allow-Origin', 'http://localhost:4000');
res.send('hello world');
})
app.listen(3000, function () {
console.log('server app listening on port 3000!')
})

设置成功后重启服务,我们可以看到这时的跨域请求已经发送成功,在client的index.html页面中已经可以看到请求得到的内容

图像Ping

1
2
3
4
5
6
7
8
9
// /client/public/src/index.js
var img = new Image();
img.onload = function() {
console.log('done');
}
img.onerror = function() {
console.log('fail');
}
img.src = 'http://localhost:3000';

通过创建一个img标签,并且设置对应的src,可以向目标url发送请求,但是这种方法无法收到返回的数据,只能发送GET请求。由于无法得到返回的数据,我们稍微改一下接口的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// server/app.js
var express = require('express')
var path = require('path')
var app = express()
app.use('/static', express.static(path.join(__dirname, 'public')))
app.get('/', function (req, res) {
- res.send('hello world');
+ console.log('receive get request');
})
app.listen(3000, function () {
console.log('server app listening on port 3000!')
})

这样我们通过命令行的输出就能够判断http://localhost:3000是否收到了请求。

刷新index.html页面后,我们可以看到控制台输出了fail,即图片加载失败,但是命令行会输出receive get request,即已经收到了通过图像Ping发送的GET请求。

JSONP

顾名思义 JSONP = JSON + Padding

不理解没关系,看下面的例子就知道了

1
2
3
4
5
6
7
// /client/public/src/index.js
function handleResponse (response) {
console.log(response.test);
}
var script = document.createElement('script');
script.src = 'http://localhost:3000/?callback=handleResponse';
document.body.insertBefore(script, document.body.firstChild);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// /server/app.js
var express = require('express')
var path = require('path')
var app = express()
app.use('/static', express.static(path.join(__dirname, 'public')))
app.get('/', function (req, res) {
- res.send('hello world');
+ res.send(req.query.callback + '(' + '{test: 1}' + ')');
})
app.listen(3000, function () {
console.log('server app listening on port 3000!')
})

不难看出,服务端接口此时返回一个字符串,这个字符串的格式为callback({test: 1}),即用一个函数包含了一个JSON,这就是JSONP名字的由来。客户端收到这个字符串后,会对其进行调用。注意这里的callback就是作为请求的参数传给接口的值,因此这个参数要和自己定义的回调函数同名。

刷新页面重新请求时,控制台打印出1。这是因为返回的handleResponse({test: 1})字符串执行后,相当于把JSON格式的数据作为参数,这样在函数内部就能获取到了。

如果实在不理解的话,可以直接访问这个链接,它会返回一个JSONP字符串。

部分选自这篇博客

转载请注明出处

坚持原创技术分享,您的支持将鼓励我继续创作!