前端開發的那些事 —— 跨域

在咱們平常開發中,常常碰到一些跨域的問題。javascript

例如www.a.com想要獲取到www.b.com的接口或者數據。就會由於同源策略的緣由而沒法獲取到。html

什麼是同源策略

同源策略 same-orgin policy 不一樣域的客戶端腳本在沒有明確受權的狀況下,不能讀寫對方的資源。前端

舉例

a.taobao.comjava

地址 是否能夠請求
a.taobao.com 不能夠-協議不一樣
www.taobao.com 不能夠-子域不一樣
taobao.com 不能夠-子域不一樣
a.taobao.com:8080 不能夠-端口不一樣
a.taobao.com/music/ 能夠-同域

CORS

CORS是一種跨域的解決方案,其原理就是前臺發送一個請求,而服務器就返回一個請求頭受權訪問。node

請求示例

  • 客戶端請求一個跨域的接口(默認帶有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 端口運行git

//前臺請求
  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

原理github

  • 經過 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 端口運行web

/** * 自動發送 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 上面的一個庫 👉 實現jsonPajax

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的數據


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

寫的很差,僅供參考!

相關文章
相關標籤/搜索