動態

詳情 返回 返回

如何解決跨域問題 - 動態 詳情

1、jsonp

jsonp解決跨域問題的本質:<script>標籤可以請求不同域名下的資源,即<script>請求不受瀏覽器同源策略影響。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>jsonp</title>
</head>
<body>
  
</body>
</html>
<script>
  var script = document.createElement('script');
  script.setAttribute("type","text/javascript");
  script.src = 'http://127.0.0.1:8888/?callback=foo';
  document.body.appendChild(script);

  function foo(data) {
    console.log(`methods: ${data.methods}, result: ${data.result}`);
    // 請求完後刪除添加到頁面上的script標籤
    var body = document.body
    body.removeChild(script)
  };
</script>

服務端:

const http = require('http')
const urlTool = require('url')

var server = http.createServer((req, res) => {
  var params = urlTool.parse(req.url, true)
  var data = {'methods': 'jsonp', 'result': 'success'}
  res.writeHead(200, {
    'Content-Type': 'text/plain'
  })
  if (params.query && params.query.callback) {
    // 服務器端應當在JSON數據前加上回調函數名,以便完成一個有效的JSONP請求
    var str = `${params.query.callback}(${JSON.stringify(data)})`
  }
  res.end(str);
})
server.listen(8888)
console.log('Server running at http://127.0.0.1:8888/')

2、CORS

跨域資源共享(CORS) 是一種機制,它使用額外的HTTP頭來告訴瀏覽器  讓運行在一個 origin (domain) 上的Web應用被准許訪問來自不同源服務器上的指定的資源。當一個資源從與該資源本身所在的服務器不同的域、協議或端口請求一個資源時,資源會發起一個跨域 HTTP 請求

在進行非簡單請求的時候,瀏覽器會先發送一次OPTION請求來“預檢”(preflight)該請求是否被允許,請求頭中會通過Access-Control-Request-MethodAccess-Control-Request-Headers來告訴服務器我需要用到的方法和字段,服務器通過返回的頭部信息中的Access-Control-Allow-OriginAccess-Control-Allow-Method來告訴瀏覽器該跨域請求是否被允許。當確認允許跨域之後,以後再發送該請求,就會省去預檢處理,之間當作簡單請求來操作。

簡單請求與非簡單請求的區別

* 請求方式:HEAD,GET,POST
* 請求頭信息:
    Accept
    Accept-Language
    Content-Language
    Last-Event-ID
    Content-Type 對應的值是以下三個中的任意一個
        application/x-www-form-urlencoded
        multipart/form-data
        text/plain

只有同時滿足以上兩個條件時,才是簡單請求,否則為非簡單請求

3、Nginx代理

3.1、正向代理和反向代理的概念

無論是正向代理,還是反向代理,説到底,就是代理模式的衍生版本罷了。我們都學習過代理設計模式,都知道代理模式中有代理角色和被代理角色,為什麼這麼説,因為這兩個角色對於我們理解正向和反向代理非常重要,下面會講到。

下面我將引入這樣一個場景,很多時候我們上網的網速特別慢,或者説由於翻牆問題導致我們無法訪問到國外的網站,通常這些情況我們會通過給瀏覽器配置一個網速快的、可以翻牆的代理ip及端口號來解決我們的問題,那麼配置好之後,大概的請求流程如下圖所示:

我們首先請求代理服務器,然後代理服務器幫我們去快速訪問國外的網站,對於這種代理方式,我們就稱之為正向代理。請記住,上面説到代理模式的兩個角色中,我們當前的角色為 被代理者,也就是瀏覽器這個角色。更重要的是,正向代理的本質是我們去請求外部的資源,如果以生產者、消費者模式來區分,我們屬於消費者。

總結:

  • 1、正向代理,我們的角色是 被代理者
  • 2、正向代理,我們不對外提供服務,反而是對外消費服務,屬於消費者

反向代理,很顯然,就是和正向代理相反,如果説正向代理是男,那麼反向代理就是女了,親,此處不再糾結其他情況!下面我用一副圖片解釋下反向代理:

看完上面的圖片,請你想象一下這麼一個場景,假設你現在是某公司技術總監,你們公司需要對外提供一套web服務,那麼你打算怎麼做呢?

答案是可以通過反向代理來完成。通常你們公司擁有自己的IDC機房,機房通訊通常採用局域網交換機,internet網用户請求是無法直接訪問到局域網內的web服務的,因此這個時候,你需要一台反向代理服務器來接收internet web請求,然後將請求分發到局域網中的不同主機上進行處理,處理完成之後再做出響應。因此,反向代理大概就是這麼一個場景。請記住,反向代理中,我們的角色是 局域網 web服務

總結:

  • 1、反向代理,我們的角色是 局域網 web服務
  • 2、反向代理,我們對外提供服務,屬於服務提供者

3.2、nginx反向代理實現跨域

3.2.1、前端配置nginx方向代理實現跨域

場景:假如前端頁面urlhttp://127.0.0.1:5500需要向後端http://127.0.0.1:3000發起請求(http://127.0.0.1:3000/index),由於瀏覽器的同源策略,該請求跨域了。前端可以配置nginx方向代理,將前端服務http://127.0.0.1:5500代理到http://127.0.0.1:3003,前端請求由http://127.0.0.1:3000/xxx改成http://127.0.0.1:3003/xxx,後端服務http://127.0.0.1:3000代理到http://127.0.0.1:3003,這樣前後端交互就同源了。核心思想就是將前後端服務代理到同一域下

// http://127.0.0.1:5500頁面
<!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>
    <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
</head>
<body>
    <button id="getOK">發送請求OK(客户端解決跨域問題)</button>
    <button id="getNO">發送請求NO(客户端解決跨域問題)</button>
    <script>
        $(document).ready(function () {
            $('#getOK').click(function () {
                $.ajax({
                    url:'http://127.0.0.1:3003/ok',
                    success:function(res) {
                        console.log("success",res)
                    },
                    error:function(err) {
                        console.log('fail',err)
                    }
                })
            })
            $('#getNO').click(function () {
                $.ajax({
                    url:'http://127.0.0.1:3003/no',
                    success:function(res) {
                        console.log("success",res)
                    },
                    error:function(err) {
                        console.log('fail',err)
                    }
                })
            })
        })
        

    </script>
</body>
</html>
// http://127.0.0.1:3000後端
const express = require('express')
const cookieParser = require('cookie-parser')

var app = express()


var router = express.Router()
router.get('/ok',function (req,res) {
    res.json({
        code:200,
        msg:"isOK"
    })    
})

router.get('/ok/son',function (req,res) {
    res.json({
        code:200,
        msg:"isOKSon"
    })    
})

router.get('/ok2',function (req,res) {
    res.json({
        code:200,
        msg:"isOK2"
    })    
})

router.get('/no',function (req,res) {
    res.json({
        code:200,
        msg:"isNO"
    })    
})

router.get('/no/son',function (req,res) {
    res.json({
        code:200,
        msg:"isNOSON"
    })    
})

router.get('/no/son2',function (req,res) {
    res.json({
        code:200,
        msg:"isNOSON2"
    })    
})

app.use(router)
app.use(cookieParser)
app.listen(3000,function () {
    console.log('listen in 3000')
})
// nginx反向代理
server {
    listen       3003;
    server_name  127.0.0.1; // 不能加上http:// ,否則會報server name "\*" has suspicious symbols

    location / {
       proxy_pass http://127.0.0.1:5500;
    } 
    location /no {
       proxy_pass http://127.0.0.1:3000;
    }
    location /ok/ {
       proxy_pass http://127.0.0.1:3000;
    }
}

效果:
image.png

3.2.2、後端配置nginx方向代理實現跨域

思想:跟前端配置nginx方向代理有所不同,後端nginx方向代理實現跨域只需要代理後端服務,並在代理服務器上配置響應頭屬性允許前端訪問代理服務器。

// http://127.0.0.1:5500 前端頁面
<!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>
    <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
</head>
<body>
    <button id="get">發送請求(服務端解決跨域問題)</button>
    <script>
        $(document).ready(function () {
            $('#get').click(function () {
                $.ajax({
                    url:'http://127.0.0.1:3002/ok',
                    //  帶cookies的請求
                    xhrFields:{
                        withCredentials:true
                    },
                    success:function(res) {
                        console.log("success",res)
                    },
                    error:function(err) {
                        console.log('fail',err)
                    }
                })
            })
        })
        

    </script>
</body>
</html>
// http://127.0.0.1:3000 後端
const express = require('express')
const cookieParser = require('cookie-parser')

var app = express()


var router = express.Router()
router.get('/ok',function (req,res) {
    res.json({
        code:200,
        msg:"isOK"
    })    
})

router.get('/ok/son',function (req,res) {
    res.json({
        code:200,
        msg:"isOKSon"
    })    
})

router.get('/ok2',function (req,res) {
    res.json({
        code:200,
        msg:"isOK2"
    })    
})

router.get('/no',function (req,res) {
    res.json({
        code:200,
        msg:"isNO"
    })    
})

router.get('/no/son',function (req,res) {
    res.json({
        code:200,
        msg:"isNOSON"
    })    
})

router.get('/no/son2',function (req,res) {
    res.json({
        code:200,
        msg:"isNOSON2"
    })    
})

app.use(router)
app.use(cookieParser)
app.listen(3000,function () {
    console.log('listen in 3000')
})
// 後端nginx方向代理
server {
    listen 3002;
    server_name 127.0.0.1;
    location /ok {
        proxy_pass http://127.0.0.1:3000;

        #   指定允許跨域的方法,*代表所有
        add_header Access-Control-Allow-Methods *;

        #   預檢命令的緩存,如果不緩存每次會發送兩次請求
        add_header Access-Control-Max-Age 3600;
        #   帶cookie請求需要加上這個字段,並設置為true
        add_header Access-Control-Allow-Credentials true;

        #   表示允許這個域跨域調用(客户端發送請求的域名和端口) 
        #   $http_origin動態獲取請求客户端請求的域   不用*的原因是帶cookie的請求不支持*號
        add_header Access-Control-Allow-Origin $http_origin;

        #   表示請求頭的字段 動態獲取
        add_header Access-Control-Allow-Headers 
        $http_access_control_request_headers;

        #   OPTIONS預檢命令,預檢命令通過時才發送請求
        #   檢查請求的類型是不是預檢命令
        if ($request_method = OPTIONS){
            return 200;
        }
    }
}

實際上不是非簡單請求的且不帶cookie只需2個字段即可解決跨域
add\_header Access-Control-Allow-Methods \*;
add\_header Access-Control-Allow-Origin $http\_origin;
效果:
image.png

window下常用nginx命令:
查看nginx進程
tasklist /fi "imagename eq nginx.exe"
殺掉nginx進程
taskkill /f /pid 16900 /pid 19012
啓動nginx
start nginx

4、postMessage

場景1:在a頁面裏打開了另一個不同源的頁面b,你想要讓a和b兩個頁面互相通信。比如,a要訪問b的LocalStorage。

場景2:你的a頁面裏的iframe的src是不同源的b頁面,你想要讓a和b兩個頁面互相通信。比如,a要訪問b的LocalStorage。

解決方案:HTML5y引入了一個全新的API,跨文檔通信 API(Cross-document messaging)。這個API為window對象新增了一個window.postMessage方法,允許跨窗口通信,不論這兩個窗口是否同源。a就可以把它的LocalStorage,發送給b,b也可以把自己的LocalStorage發給a。

window.postMessage(message, targetOrigin, [transfer]),有三個參數:

message是向目標窗口發送的數據;
targetOrigin屬性來指定哪些窗口能接收到消息事件,其值可以是字符串"*"(表示無限制)或者一個URI(或者説是發送消息的目標域名);
transfer可選參數,是一串和message 同時傳遞的 Transferable 對象. 這些對象的所有權將被轉移給消息的接收方,而發送一方將不再保有所有權。

另外消息的接收方必須有監聽事件,否則發送消息時就會報錯。The target origin provided ('http://localhost:8080') does not match the recipient window's origin ('http://localhost:63343').

window.addEventListener("message",onmessage);onmessage接收到的message事件包含三個屬性:

data:從其他 window 中傳遞過來的數據。
origin:調用 postMessage 時消息發送方窗口的 origin 。請注意,這個origin不能保證是該窗口的當前或未來origin,因為postMessage被調用後可能被導航到不同的位置。
source:對發送消息的窗口對象的引用; 您可以使用此來在具有不同origin的兩個窗口之間建立雙向通信。

案例
使用http-server模擬不同源的兩個頁面

// http://127.0.0.1:8080/A.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>
  <button onclick="send()">向彈窗頁發送信息</button>
</body>

</html>

<script>
  var popup;
  window.onload = function () {
    popup = window.open('http://127.0.0.1:8081/C.html', '', 'width=500,height=500')
  }

  function send() {
    popup.postMessage('Hello World!', 'http://127.0.0.1:8081/')
  }
</script>
// http://127.0.0.1:8081/C.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>
    <div id="app"></div>
</body>

</html>

<script>
    window.onload = function () {
        console.log('recieve')
        window.addEventListener("message", onmessage);
    }
    function onmessage(event) {
        if (event.origin == "http://127.0.0.1:8080") { // http://localhost:8080是發送方a的域名
            document.getElementById('app').innerText = event.data
        }
    }
</script>

5、document.domain

6、window.name

場景1:現在瀏覽器的一個標籤頁裏打開http://www.damonare.cn/a.html頁面,你通過location.href=http://baidu.com/b.html,在同一個瀏覽器標籤頁裏打開了不同域名下的頁面。這時候這兩個頁面你可以使用window.name來傳遞參數。因為window.name指的是瀏覽器窗口的名字,只要瀏覽器窗口相同,那麼無論在哪個網頁裏訪問值都是一樣的。

場景2:你的http://www.damonare.cn/a.html頁面裏使用<iframe>調用另一個http://baidu.com/b.html頁面。這時候你想在a頁面裏獲取b頁面裏的dom,然後進行操作。然後你會發現你不能獲得b的dom。同樣會因為不同源而報錯,和上面提到的不同之處就是兩個頁面的一級域名也不相同。這時候document.domain就解決不了了。

解決方案:瀏覽器窗口有window.name屬性。這個屬性的最大特點是,無論是否同源,只要在同一個窗口裏,前一個網頁設置了這個屬性,後一個網頁可以讀取它。。比如你在b頁面裏設定window.name="hello",你再返回到a頁面,在a頁面裏訪問window.name,可以得到hello

這種方法的優點是,window.name容量很大,可以放置非常長的字符串;缺點是必須監聽子窗口window.name屬性的變化,影響網頁性能。

// http://127.0.0.1:8080
<!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>
  <p></p>
  <iframe src="http://127.0.0.1:8081/demo10.html" frameborder="0"></iframe>
</body>
</html>
<script>
  var ifr = document.getElementsByTagName('iframe')[0]
  ifr.onload = function() {
    document.getElementsByTagName('p')[0].innerText = ifr.contentWindow.name
  }
</script>
// http://127.0.0.1:8081
<!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>
    window.name="hello, world!"
  </script>
</body>
</html>

上面執行結果會報錯,因為不同源
image.png
image.png
解決方法:

<script>
  var ifr = document.getElementsByTagName('iframe')[0]
  ifr.onload = function() {
    ifr.onload = function(){
      document.getElementsByTagName('p')[0].innerText = ifr.contentWindow.name
    }
    ifr.src = 'about:blank';
  }
</script>

參考:
https://segmentfault.com/a/11...
https://segmentfault.com/a/11...
https://juejin.im/entry/5b3a2...
https://juejin.im/post/5b67b3...
https://juejin.im/post/5815f4...

user avatar pantao 頭像 guoduandemuer 頭像 nihaojob 頭像 panpanpanya 頭像 jinl9s27 頭像 juanerma 頭像 yuanliangwoyishengbufanggegaowenyi 頭像 initsec 頭像 yangyangyang_62148b0d0b9ce 頭像 ruochuan12 頭像
點贊 10 用戶, 點贊了這篇動態!
點贊

Add a new 評論

Some HTML is okay.