前端多種跨域方式實現原理詳解

跨域是咱們在項目中常常遇到的,先後端數據交互常常碰到請求跨域,首先咱們來想一下爲何會有跨域這個詞的出現?本文帶你來探討一下如下幾個問題:javascript

  • 跨域是什麼?
  • 爲何要跨域?
  • 跨域的幾種方式?
  • ...

什麼是跨域?

跨域是指的瀏覽器不能執行其它網站的腳本,它是由瀏覽器的同源策略形成,是瀏覽器對JavaScript實施的安全限制。html

跨域實際上指從一個域的網頁去請求另外一個域的資源,好比:從 http://www.baidu.com 網站去請求http://www.google.com 網站的資源。前端

什麼是同源策略?

同源策略 指的是 域名協議端口 三者都相同~
java

什麼是同源?

要知道URL協議域名端口以及路徑組成,若兩個URL的協議、域名和端口相同,則表示他們同源。
相反,只要協議域名端口有任何一個的不一樣,就被看成是跨域。node

限制同源策略內容

  • Cookie、LocalStorage、IndexedDB等存儲性內容
  • DOM節點
  • Ajax請求發送後,結果被瀏覽器攔截了

容許跨域加載資源

這下邊三個含有 src 標籤的是容許跨域加載資源的jquery

<img src=XXX>
<link href=XXX>
<script src=XXX>

跨域的場景

九種跨域解決方案

  • jsonp
  • cors
  • postMessage
  • document.domain
  • window.name
  • location.hash
  • https-proxy
  • nginx
  • websocket

jsonp

什麼是jsonp

jsonp全稱是JSON with Padding,是爲了解決跨域請求資源而產生的解決方案,是一種依賴開發人員創造出的一種非官方跨域數據交互協議。nginx

Jsonp的原理

  1. 利用script標籤的src屬性來實現跨域
  2. 經過將前端方法做爲參數傳遞到服務器端,而後由服務器端注入參數以後再返回,實現服務器端向客戶端通訊
  3. 因爲使用script標籤的src屬性,所以只支持get方法

Jsonp和Ajax對比

  1. Jsonp和Ajax相同,都是客戶端向服務器端發送請求,從服務器端獲取數據的方式
  2. Ajax屬於同源策略
  3. Jsonp屬於非同源策略(跨域請求)

Jsonp的優缺點

優勢:web

  1. 它不像XMLHttpRequest對象實現的Ajax請求那樣受到同源策略的限制,JSONP能夠跨越同源策略
  2. 它的兼容性更好,在更加古老的瀏覽器中均可以運行,不須要XMLHttpRequest或ActiveX的支持
  3. 在請求完畢後能夠經過調用callback的方式回傳結果

缺點:ajax

  1. 它只支持GET請求而不支持POST等其它類型的HTTP請求
  2. 它只支持跨域HTTP請求這種狀況,不能解決不一樣域的兩個頁面之間如何進行JavaScript調用的問題
  3. jsonp在調用失敗的時候不會返回各類HTTP狀態碼
  4. 缺點是安全性,萬一假如提供jsonp的服務存在頁面注入漏洞,即它返回的javascript的內容被人控制的

Jsonp的實現流程

  1. 聲明一個回調函數,把函數名(show)當作參數值
  2. 要傳遞給跨域請求的數據的服務器,函數形參爲要獲取目標數據
  3. 建立一個script標籤,把那個跨域的API數據接口地址,賦值給script的src,還要在這個地址中向服務器傳遞該函數名
  4. 服務器接收到請求後,須要進行處理:把傳遞的參數名和它須要的數據拼接成一個字符串
  5. 最後服務器把準備的數據經過HTTP協議返回給客戶端,客戶端再調用執行以前聲明的回調函數(show),對返回的數據進行操做

具體代碼實現以下:chrome

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script>
        function jsonp({url,params,cb}){
            return new Promise((resolve,reject)=>{
                let script = document.createElement('script');
                window[cb]=function(data){
                    resolve(data);
                    document.body.removeChild(script);
                }
                params={...params,cb}//wd=b&cb=show
                let arrs = [];
                for(let key in params){
                    arrs.push(`${key}=${params[key]}`);
                }
                script.src = `${url}?${arrs.join('&')}`;
                document.body.appendChild(script)
            })
        }
        //只能發送get請求,不支持post put delete
        //不安全xss攻擊  不採用
        jsonp({
            url:'http://localhost:3000/say',
            params:{wd:'早上好'},
            cb:'show'
        }).then(data=>{
            console.log(data)
        })
    </script>
</body>
</html>

serve.js

let express = require('express');
let app = express();

app.get('/say',function (req,res){
    let {wd,cb} = req.query;
    console.log(wd);
    res.end(`${cb}('晚上好')`)
})
app.listen(3000)

注意: 須要安裝npm install express, 而後在終端裏面輸入node serve.js, 再把index.html在瀏覽器上邊console欄查看返回結果

JQuery的jsonp跨域請求

若是從 192.168.19.1ajax請求到 192.168.19.6 會產生跨域問題, 利用jqueryjsonp參數可輕鬆這個問題。

注意:Jsonp都是GET和異步請求

function get() {
    $.ajax({
           type: "GET",
           url: 'http://192.168.19.6:8080/jsgd/bill.jsp?userCode=?&date='+ new Date(), 
           dataType:"jsonp",
                jsonp:"jsonpcallback",
          success: function(msg){
        $('#callcenter').html(msg.text);
           }
    });
}

cors

什麼是cors

cors全稱"跨域資源共享"(Cross-origin resource sharing), 是一種ajax跨域請求資源的方式。

兼容性

  1. cors須要瀏覽器和服務器同時支持,才能夠實現跨域的請求
  2. 這個方法幾乎全部的瀏覽器都支持,可是ie必須是10以上
  3. ie8和9須要經過XDomainRequest來實現

請求類型

cors分爲簡單請求複雜請求兩類

簡單請求

請求方式使用下列方法之一:

GET
HEAD
POST

Content-Type的值僅限於下列三者之一:

text/plain
multipart/form-data
application/x-www-form-urlencoded

注意:對於簡單的請求,瀏覽器會直接發送cors請求,具體來講就是在header中加入origin請求頭字段。在響應頭回服務器設置相關的cors請求,響應頭字段爲容許跨域請求的源。請求時瀏覽器在請求頭的Origin中說明請求的源,服務器收到後發現容許該源跨域請求,則會成功返回。

複雜請求

使用了下面任一HTTP方法

PUT
DELETE
CONNECT
OPTIONS
TRACE
PATCH

Content-Type的值不屬於下列之一:

application/x-www-form-urlencoded
multipart/form-data
text/plain

當符合複雜請求的條件時,瀏覽器會自動先發送一個options請求。若是發現瀏覽器支持該請求,則會將真正的請求發送到後端。若是瀏覽器發現服務端不支持該請求,則會在控制檯拋出錯誤。

cors字段介紹

  • Access-Control-Allow-Methods

    這個字段是必要的,它的值是逗號分割的一個字符串,代表服務器支持的全部跨域請求的方式

  • Access-Control-Allow-Headers

    若是瀏覽器請求包括這個字段,則這個字段也是必須的,它也是一個逗號分隔的字符串,代表服務器支持的全部頭信息字段,不限於瀏覽器在「預檢"中請求的字段

    • Access-Control-Allow-Credentials

      這個字段與簡單請求時的含義相同

  • Access-Control-Max-Age

    該字段可選,用來指定本次預檢請求的有效期,單位爲秒。上面結果中,有效期是20天(1728000秒),即容許緩存該條迴應1728000秒(即20天),在此期間,不用發出另外一條預檢請求

流程實現

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    hello
</body>
</html>

serve.js

let express = require('express');
let app = express();
app.use(express.static(__dirname));

app.listen(3000)

以當前這個做爲靜態文件目錄,先要在終端裏面node serve.js服務器打開,訪問localhost:3000就能夠把 hello 顯示出來。

這是一個完整的複雜請求例子:

index.js

let xhr = new XMLHttpRequest()
document.cookie = 'name=xiaoming'
xhr.withCredentials = true
xhr.open('PUT', 'http://localhost:4000/getData', true)
xhr.setRequestHeader('name', 'xiaoming')
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
    if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
    console.log(xhr.response)
    //獲得響應頭,後臺需設置Access-Control-Expose-Headers
    console.log(xhr.getResponseHeader('name'))
    }
}
}
xhr.send()

serve.js

let express = require('express');
let app = express();
app.use(express.static(__dirname));

app.listen(3000)

serve2.js

let express = require('express')
let app = express()
let whitList = ['http://localhost:3000']
app.use(function(req, res, next) {
  let origin = req.headers.origin
  if (whitList.includes(origin)) {
    // 設置哪一個源能夠訪問我
    res.setHeader('Access-Control-Allow-Origin', origin)
    // 容許攜帶哪一個頭訪問我
    res.setHeader('Access-Control-Allow-Headers', 'name')
    // 容許哪一個方法訪問我
    res.setHeader('Access-Control-Allow-Methods', 'PUT')
    // 容許攜帶cookie
    res.setHeader('Access-Control-Allow-Credentials', true)
    // 預檢的存活時間
    res.setHeader('Access-Control-Max-Age', 6)
    // 容許返回的頭
    res.setHeader('Access-Control-Expose-Headers', 'name')
    if (req.method === 'OPTIONS') {
      res.end() // OPTIONS請求不作任何處理
    }
  }
  next()
})
app.put('/getData', function(req, res) {
  console.log(req.headers)
  res.setHeader('name', 'ming') //返回一個響應頭,後臺需設置
  res.end('早上好')
})
app.get('/getData', function(req, res) {
  console.log(req.headers)
  res.end('早上好')
})
app.use(express.static(__dirname))
app.listen(4000)

Cors與Jsonp比較

  1. cors比Jsonp更強大
  2. Jsonp只支持Get請求,cors支持全部類型的HTTP請求
  3. 使用cors,可使用XMLHttpRequest發起請求和獲取數據,比Jsonp有更好的錯誤處理
  4. Jsonp的優點在於支持老式瀏覽器和能夠向cors的網絡請求數據
  5. cors與Jsonp相比,更方即可靠

postMessage

什麼是postMessage

postMessage方法容許來自不一樣源的腳本採用異步方式進行有限的通訊,能夠實現跨文本檔、多窗口、跨域消息傳遞。

postMessage語法

otherWindow.postMessage(message, targetOrigin, [transfer])
  • otherWindow:其它窗口(目標窗口)的引用,好比iframe的contentWindow屬性、執行window.open返回的窗口對象、或者是命名過或數值索引的window.frames
  • message:將要發送到其餘window的數據,這個數據會自動被序列化,數據格式也不受限制(字符串,對象均可以)
  • targetOrigin:目標窗口的源,能夠是字符串*表示無限制,或URL,須要協議端口號和主機都匹配纔會發送
  • transfer(可選):是一串和message同時傳遞的Tranferable對象,這些對象的全部權將 被轉移給消息接收方,而發送一方將再也不保有全部權

兼容性

高級瀏覽器Internet Explorer 8+, chromeFirefox , OperaSafari 都將支持這個功能

流程實現

a.html 向 b.html傳遞 "早上好",而後 a.html 傳回"今每天氣真好"

a.html

<iframe src="http://localhost:4000/b.html" frameborder="0" id="frame" onload="load()"></iframe>
<script>
function load(){
    let frame = document.getElementById('frame');
    frame.contentWindow.postMessage('早上好','http://localhost:4000');
    window.onmessage=function(e){
        console.log(e.data)
    }
}
</script>

b.html

<script>
    window.onmessage = function(e){
        console.log(e.data);
        e.source.postMessage('今每天氣不錯',e.origin)
    }
</script>

a.js

let express = require('express')
let app = express();
app.use(express.static(__dirname));
app.listen(3000)

b.js

let express = require('express')
let app = express();
app.use(express.static(__dirname));
app.listen(4000)

Window.name

什麼是Window.name

window.name 是一個 window 對象的內置屬性,name 屬性能夠設置或返回存放窗口的名稱的一個字符串。

該屬性的特徵

在頁面在瀏覽器端展現的時候,咱們總能在控制檯拿到一個全局變量window,該變量有一個name屬性,有如下的特徵:

  1. 每一個窗口都有獨立的window.name與之對應
  2. 在一個窗口的生命週期中(被關閉前),窗口載入的全部頁面同時共享一個window.name,每一個頁面window.name都有讀寫的權限
  3. window.name一直存在與當前窗口,即便是新的頁面載入也不會改變window.name的值
  4. window.name能夠存儲不超過2M的數據,數據個數按需自定義

流程實現

  1. a.html和b.html是同域 http://localhost:3000
  2. c.html是獨立的 http://localhost:4000
  3. a獲取c的數據
  4. a先引用c
  5. c把值放到window.name,把a引用的地址改成b

a.html

<iframe src="http://localhost:4000/c.html" frameborder="0" onload="load()" id="iframe"></iframe>
<script>
    let first = true
    function load(){
        if(first){
            let iframe = document.getElementById('iframe');
            iframe.src="http://localhost:3000/b.html";
            first = false;
        }else{
            console.log(iframe.contentWindow.name)
            }
        }
<script>

b.html

<body>
    早上好
</body>

c.html

<script>
        window.name='今每天氣不錯'
    </script>

a.js

let express = require('express')
let app = express();
app.use(express.static(__dirname));
app.listen(3000)

b.js

let express = require('express')
let app = express();
app.use(express.static(__dirname));
app.listen(4000)

location.hash

什麼是location.hash

locationjavascript裏面的內置對象,如location.href就管理頁面的url,用loaction.href=url就能夠直接將頁面重定向url,而location.hash則能夠用來獲取或設置頁面的標籤值,hash屬性是一個可讀可寫的字符串,該字符串是URL的錨部分(從#號開始的部分)

location.hash的簡單應用

#的含義

#表明網頁中的位置,其右邊的字符,就是該位置的標識符,例如:

http://www.juejin.com/index.html#drafts

就是表明index.html的drafts位置,瀏覽器讀取這個URL後,會自動將print位置滾動至可視區域

HTTP請求不包括#

#是用來指導瀏覽器的動做的,對服務器端徹底無用,因此,HTTP請求中不包括#
例如:

http://www.juejin.com/index.html#drafts

瀏覽器實際發出的請求是這樣的:

GET/index.html HTTP/1.1
Host:www.juejin.com

能夠看到,只是請求的index.html,沒有#drafts部分

#後的字符

在第一個#出現的任何字符,都會被瀏覽器解讀爲位置標識符,這意味着,這些字符不會被髮送到服務器端

改變#不觸發網頁重構

單單改變#後的部分,瀏覽器只會滾動到相應的位置,不會從新加載網頁

改變#會改變瀏覽器的訪問歷史

每一次改變#後的部分,都會在瀏覽器的訪問歷史中增長一個記錄,使用"後退"按鈕,就能夠回到上一個位置

讀取#值

window.location.hash這個屬性可讀可寫。讀取時,能夠用來判斷網頁狀態是否改變;寫入時,則會在不重載網頁的前提下,創造一條訪問歷史記錄

onhashchange事件

當#值發生變化時,就會觸發這個事件。IE8+、Firefox 3.6+、Chrome 5+、Safari 4.0+支持該事件

Google抓取#的機制

默認狀況下,Google的網絡忽視URL的#部分

流程實現

路徑後面的hash值能夠用來通訊。目的:a.html 想訪問 c.html跨域相互通訊。

  1. a.html給c.html傳一個hash值,須要經過中間的b.html來實現
  2. c.html收到hash值後 c.html把hash值傳遞給b.html
  3. b.html將結果放到a.html的hash值中

a.html

<iframe src="http://localhost:4000/c.html#goodmorning"></iframe>
<script>
  window.onhashchange = function () {
    console.log(location.hash);
  }
</script>

b.html

<script>
    window.parent.parent.location.hash = location.hash 
</script>

c.html

<script>
    console.log(location.hash);
    let iframe = document.createElement('iframe');
    iframe.src = 'http://localhost:3000/b.html#goodevening';
    document.body.appendChild(iframe);
</script>

a.js

let express = require('express')
let app = express();
app.use(express.static(__dirname));
app.listen(3000)

b.js

let express = require('express')
let app = express();
app.use(express.static(__dirname));
app.listen(4000)

domain

什麼是domain

主要用於主域相同的域之間的數據通訊,注意 僅限主域相同,子域不一樣的跨域應用場景。

實現的原理:兩個頁面都經過js強制設置 document.domain 爲基礎主域,就實現了同域

說明

這個方法只能用於二級域名相同的狀況下,好比:

www.baidu.com
hhh.baidu.com

這就適用於domain方法

流程實現

a.html

<iframe src="http://b.ming.cn:3000/b.html" frameborder="0" onload="load()" id="frame"></iframe>
  <script>
    document.domain = 'ming.cn'
    function load() {
      console.log(frame.contentWindow.a);
    }
  </script>

b.html

<div>
    早上好啊
</div>
<script type="text/javascript">
     document.domain = 'ming.cn'
     var a = 99999;
</script>

a.js

let express = require('express')
let app = express();
app.use(express.static(__dirname));
app.listen(3000, () => {
    console.log('server run at 3000')
})

b.js

let express = require('express')
let app = express();
app.use(express.static(__dirname));
app.listen(4000, () => {
    console.log('server run at 4000')
})


這個就能夠經過http://a.ming.cn:3000/a.html獲取到頁面http://a.ming.cn:3000/b.htm中的a的值99999

注意:這裏我把我電腦上邊的hosts修改了一下,否則不能出來效果

WebSocket

什麼是WebSocket

WebSocket是一種網絡通訊協議,它實現了瀏覽器與服務器全雙工通訊,同時容許跨域通信。原生WebSocket API使用起來不太方便,咱們使用Socket.io,它很好地封裝了webSocket接口,提供了更簡單、靈活的接口,也對不支持webSocket的瀏覽器提供了向下兼容。

WebSocket如何工做

Web瀏覽器和服務器都必須實現 WebSockets 協議來創建和維護鏈接。因爲 WebSockets 鏈接長期存在,與典型的HTTP鏈接不一樣,對服務器有重要的影響。

注意:基於多線程或多進程的服務器沒法適用於WebSockets,由於它旨在打開鏈接,儘量快地處理請求,而後關閉鏈接。任何實際的WebSockets服務器端實現都須要一個異步服務器。

流程實現

a.html

<script>
    //高級api 不兼容 socket.io(通常使用它)
        let socket = new WebSocket('ws://localhost:3000');
        socket.onopen=function(){
            socket.send('早上好啊')
        }
        socket.onmessage = function(e){
            console.log(e.data);
        }
    </script>

a.js

let express = require('express')
let app = express();
let WebSocket = require('ws')
let wss = new WebSocket.Server({port:3000})
wss.on('connection',function(ws){
    ws.on('message',function(data){
        console.log(data)
        ws.send('今每天氣真好')
    })
})


總結

以上就是整理的一些跨域的方法,我以爲通常用cors,jsonp等常見的方法就能夠了,不過遇到了一些特殊狀況,咱們也要作到有不少方法是能夠選擇的,相信這篇文字會對你們有幫助!

歡迎你們加入❤️❤️❤️
cmd-markdown-logo

參考文章

珠峯架構課

相關文章
相關標籤/搜索