前端那些事 —— 跨域

在我们日常开发中,经常碰到一些跨域的问题。

例如www.a.com想要获取到www.b.com的接口或者数据。就会因为同源策略的原因而无法获取到。

什么是同源策略

同源策略 same-orgin policy
不同域的客户端脚本在没有明确授权的情况下,不能读写对方的资源。

举例

http://a.taobao.com

地址是否可以请求
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的实现,面试中也会常常被问到,并且手撸一个jsonP

具体的一个方法实现推荐一个 github 上面的一个库 ? 实现jsonP

iframe

原理

我们以三个页面为例和一个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.

ajax-error

接下来我们修改一下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.htmlupdate的方法并把window.name值一同传递

此时我们就大功告成啦,页面显示出data.json的数据


以上就是我分享的前端跨域请求方式!

写的不好,仅供参考!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值