在我们日常开发中,经常碰到一些跨域的问题。
例如www.a.com
想要获取到www.b.com
的接口或者数据。就会因为同源策略的原因而无法获取到。
什么是同源策略
同源策略 same-orgin policy
不同域的客户端脚本在没有明确授权的情况下,不能读写对方的资源。
举例
地址 | 是否可以请求 |
---|---|
https://a.taobao.com | 不可以-协议不同 |
http://www.taobao.com | 不可以-子域不同 |
http://taobao.com | 不可以-子域不同 |
http://a.taobao.com:8080 | 不可以-端口不同 |
http://a.taobao.com/music/ | 可以-同域 |
CORS
CORS是一种跨域的解决方案,其原理就是前台发送一个请求,而服务器就返回一个请求头授权访问。
- 客户端请求一个跨域的接口(默认带有Origin请求头)
- 服务端收到后设置一个响应头并返回(Access-Control-Allow-Origin)授权
// 后台koa脚本
const koa = require('koa');
const bodyParser = require('koa-bodyparser');
const app = new koa();
//使用bodyParser
app.use(bodyParser());
app.use(async ctx =>{
const url = ctx.url;
if(ctx.headers.origin && ctx.query.cors){
//设置请求头
ctx.set('Access-Control-Allow-origin',ctx.headers.origin)
}
let res = {
code:0,
data:'success'
}
ctx.body = JSON.stringify(res);
})
app.listen(3000,()=>{
console.log('服务器冲起来了');
})
后台在
node
3000的端口运行,前台在http-server
3001 端口运行
//前台请求
var $ = (id) => document.getElementById(id);
var btn = $('button1');
var btn2 = $('button2');
function getData(callback, cors) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
if (xhr.readyState === 4 && xhr.responseText) {
callback(JSON.parse(xhr.responseText), xhr);
}
}
xhr.open('get', `http://127.0.0.1:3000/${cors}`);
xhr.send(null);
}
btn.addEventListener('click', () => {
getData((response) => {
console.log(`${response.data}`)
}, '');
});
btn2.addEventListener('click', () => {
getData((response) => {
console.log(`${response.data}`)
}, '?cors=1');
});
- CORS 优点在于方便简洁,只需要服务端设置响应头就好了。
- 缺点在与一些IE不兼容 具体可以看 ? CORS兼容性
JSONP
原理
- 通过
script
进行请求通过src请求 - 创建一个回调函数,然后在远程服务上调用这个函数并且将JSON 数据形式作为参数传递
- 将JSON数据填充进回调函数
//koa
const Koa = require('koa')
const bodyParser = require('koa-bodyparser')
const app = new Koa()
// 使用bodyParser
app.use(bodyParser())
app.use(async ctx => {
const url = ctx.url
if (url.indexOf('/getData') === 0) { // 接口名称
ctx.set('Content-Type', 'application/x-javascript')
let res = {
code:0,
data:"我是一个jsonP跨域的数据!"
}
ctx.body = `${ctx.query.callback || 'jsonp'}(${JSON.stringify(res)})`
} else {
ctx.status = 404
ctx.body = '404'
}
})
app.listen(3000, () => {
console.log('服务启动,打开 http://127.0.0.1:3000/')
})
后台在
node
3000的端口运行,前台在http-server
8081 端口运行
/**
* 自动发送 jsonp
* @param {String} url
* @param {Obj} data
* @param {Function} callback
*/
function jsonp (url, data, callback) {
var funcName = getFunctionName()
data = data || {}
data.callback = funcName
url = parseUrl(url, serialize(data))
window[funcName] = function (response) {
// 这里可以看情况处理,比如如果是 jsonp 我们可以 parse 一下
// data = JSON.parse(response)
callback(response)
}
createScript(url)
}
/**
* 序列化参数
* jsonp 中参数只能是 GET 方式传递
* @param {Obj} data
*/
function serialize (data) {
var ret = []
Object.keys(data).forEach(item => {
ret.push(encodeURIComponent(item) + '=' + encodeURIComponent(data[item]))
})
return ret.join('&')
}
/**
* 处理 URL ,把参数拼上来
* @param {String} url
* @param {String} param
*/
function parseUrl (url, param) {
return url + (url.indexOf('?') === -1 ? '?' : '&') + param
}
/**
* 必须要有一个全局函数,而且不能重名
*/
function getFunctionName () {
return ('jsonp_' + Math.random()).replace('.', '')
}
/**
* 创建 script 标签并插到 body 中
* @param {String} url
*/
function createScript (url) {
var doc = document
var script = doc.createElement('script')
script.src = url
doc.body.appendChild(script)
}
//调用
jsonp('http://127.0.0.1:3000/getData', {id:1}, (data) => {
console.log(data)
})
这是一个简单版的
jsonP
的实现,面试中也会常常被问到,并且手撸一个jsonP
具体的一个方法实现推荐一个 github 上面的一个库 ? 实现jsonP
iframe
原理
- 借助iframe标签进行跨域
- 将获取到的数据挂载到window.name
我们以三个页面为例和一个json为例
- a.html -> 在端口 3001 运行
- b.html -> 在端口 3002 运行
- c.html
- data.json
- c去去掉用父级的方法,并传递获取到的数据。
大概的交互图为这样 ?
a为请求数据
<!--a.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>我是A页面</title>
</head>
<body>
<div></div>
<script>
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
console.log(xhr.responseText);
} else {
console.log(xhr.status, xhr.statusText);
}
}
xhr.open('get', 'http://127.0.0.1:3002/data.json');
xhr.send(null);
</script>
</body>
</html>
b为处理数据
<!--b.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>处理数据</title>
</head>
<body>
<script>
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
window.name = xhr.responseText;
location.href = 'http://127.0.0.1:3001/c.html';
} else {
console.log(xhr.status, xhr.statusText);
}
}
xhr.open('get', 'data.json');
xhr.send(null);
</script>
</body>
</html>
c为更新数据
<!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>传递数据</title>
</head>
<body>
<script>
parent.update(window.name);
</script>
</body>
</html>
data数据
[
{
"data": "我是一条跨域数据"
}
]
当我们调用a.html第一个AJAX方法的时候,毫无疑问肯定是跨域的,因为端口不一样
Access to XMLHttpRequest at ‘http://127.0.0.1:3002/data.json’ from origin ‘http://127.0.0.1:3001’ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource.
接下来我们修改一下A 的代码
<!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>我是A页面</title>
</head>
<body>
<div></div>
<iframe src="http://127.0.0.1:3002/b.html" frameborder="0"></iframe>
<script>
function update(data) {
data = JSON.parse(data);
var html = ['<ul>'];
html.push(`<li>${data[0].data}</li>`);
html.push('</ul>');
document.querySelector('div').innerHTML = html.join('');
}
</script>
</body>
</html>
在改变了逻辑时
a.html
嵌套了一个与接口一样端口的网页b.html
通过同端口的方式请求到了想用端口的数据,并且挂载到了全局的window.name
上,并且跳转到c.html
c.html
通过parent
调用a.html
的update
的方法并把window.name
值一同传递
此时我们就大功告成啦,页面显示出data.json
的数据
以上就是我分享的前端跨域请求方式!
写的不好,仅供参考!