「跨域」利用node.js實踐前端各類跨域方式(上)

前言

常言道,"讀萬卷書,不如行萬里路"。技術的學習也是如此,惟有實踐才能更清楚的明白原理和加深印象,所以本文會利用node.js對前端的各類跨域方式進行實踐,強烈建議一步一步跟着作,相信你確定會對跨域有更深層次的理解。而因爲篇幅限制,本文只會貼出關鍵性的代碼,本系列總共分爲上下篇。具體的代碼請移步個人Github。若是對你有幫助的話,歡迎 star ヾ(´・ω・`)ノcss

1、cors 跨域

首先咱們在本地起一個服務器,用於接收客戶端的請求並做出迴應。html

//目錄:cors/server.js前端

const http = require('http');

http.createServer(function (req, res) {
    //設置響應頭部
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.write('This is a server page');
    res.end();
  }).listen(3333);
   console.log('server start!')

而後,開啓另外一個服務,服務里加載一個html頁面,頁面對發出xhr請求,模擬瀏覽器對服務器的請求。node

//目錄:cors/clientServer.jsjquery

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

app.use(express.static('./public'));
app.listen(3000)
console.log('client server start');

//目錄:cors/public/client.htmlgit

const  content = document.getElementById('content');

            const xhr = new XMLHttpRequest();
            xhr.withCredentials = true;
            xhr.onload = function(){
                if(xhr.readyState == 4) {
                    if(xhr.status >= 200 && xhr.status <300 || xhr.status == 304) {
                        content.innerHTML = 'Reuqest was success:' + xhr.responseText;
                        console.log('Request was success:', xhr.responseText);
                    }else {
                        content.innerHTML = 'Reuqest was failed:' + xhr.status;
                        console.log("Request was failed:", xhr.status); 
                    }
                }
            }
            // xhr.open('get', 'http://localhost:3000/client.html', true); //不跨域
            xhr.open('get', 'http://localhost:3333', true); //跨域

            xhr.send();

分別運行兩個服務,測試3000和3333接口,發現只有跨域的時候,請求的頭部纔會帶着origin字段。此時咱們修改cors/server.js, 加上這行代碼:github

res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3000');

這行代碼表明服務器容許接收來自3000接口的請求,此時客戶端再次請求服務器,就能在用戶毫無感知的狀況下完成跨域。web

而此時若是想讓客戶端帶cookie請求呢?那麼須要作如下工做:ajax

1.cors/server.js 加上這行express

res.setHeader('Access-Control-Allow-Credentials', true);

2.cors/public/client.html 加上這行

xhr.withCredentials = true;

而後,你就會發現,客戶端會把當前域下的cookie一塊兒發給服務器啦╮( ̄▽ ̄")╭

ps:注意cookie只能細分到域名下,不能細分到端口。即沒辦法設置一個cookie僅在localhost:xxxx下。儘管端口不一樣會被瀏覽器認爲不一樣源。

2、jsonp跨域

一般爲了減輕web服務器的負載,咱們把js、css,img等靜態資源分離到另外一臺獨立域名的服務器上,在html頁面中再經過相應的標籤從不一樣域名下加載靜態資源,而被瀏覽器容許,基於此原理,咱們能夠經過動態建立script,再請求一個帶參網址實現跨域通訊。

下面這個例子採用jQuery中的ajax方法,與服務器端約定將數據回傳到回調函數中,好比本例中的callback=person,而後咱們就能夠從回調函數person裏獲取服務器傳給瀏覽器的數據了。另外,jsonp的缺點就是隻能採用get請求。

1.目錄:jsonp/server.js

const http = require('http');
const urllib = require('url');
const  httpdispatcher = require('httpdispatcher');
const dispatcher = new httpdispatcher();

const PORT = 1112;

function handleRequest(req, res) {
    try {
        console.log(req.url);

        dispatcher.dispatch(req, res);
    }catch(err) {
        console.log(err);
    }
}

const server = http.createServer(handleRequest);

dispatcher.onGet('/getPerson', function (req, res, next) {
  const data = {'name': 'Jchermy', 'company': 'dog company'};
  const params = urllib.parse(req.url, true);

  if(params.query && params.query.callback) {
      let str = `${params.query.callback}(${JSON.stringify(data)})`;
      res.write(str);
      res.end();
  }else {
      res.write(JSON.stringify(data));
      res.end();
  }
})

server.listen(PORT, function () {
    console.log("server listening on http://localhost: %s", PORT);
  })

2.目錄:jsonp/client.js

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

app.use(express.static('./public'));
app.listen(1111);
console.log('client start');

3.目錄:jsonp/public/index.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>index</title>
    <script src="http://apps.bdimg.com/libs/jquery/2.1.4/jquery.js"></script>
</head>
<body>
    <button id='getData'>跨域獲取數據</button>
    <div id='content'>
        姓名:<span class="name"></span><br/>
        公司:<span class="company"></span>
    </div>
    <script>
        const btn = document.getElementById('getData');
        const container = document.getElementById('content');
        btn.addEventListener('click', function(){
            $.ajax({
                url: 'http://localhost:1112/getPerson?callback=?',
                dataType: 'jsonp',
                jsonpCallback: 'person',
                success: function(data){
                    $('.name').html(data.name);
                    $('.company').html(data.company);
                }
            })
        }, false);
    </script>
</body>
</html>

分別運行客戶端和服務端,點擊「獲取跨域數據的按鈕」,當前頁面(1111端口)就能夠拿到1112端口的數據啦~~(●′ω`●)

3、document.domain + frame 跨域

此方案僅限主域相同,子域不一樣的跨域應用場景。

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

下面只是舉個例子幫助你們理解一下。

如今有兩個網址。百度知道和百度百科

https://zhidao.baidu.com/
https://baike.baidu.com/

在百度知道的網頁,寫下如下命令:

document.domain = "baidu.com";
const child= window.open("https://baike.baidu.com/");

在打開的百度百科的網頁,寫下如下命令:

document.domain = "baidu.com";

而後回到百度知道的網頁,就能夠獲取到百度百科(子頁面)的元素啦:

const button = other.document.getElementById("search");

//<button id="search" nslog="normal" nslog-type="10080008" type="button">進入詞條</button>

4、window.name+iframe 跨域

window.name屬性的獨特之處:name值在不一樣的頁面(甚至不一樣域名)加載後依舊存在,而且能夠支持很是長的 name 值(2MB)。

在本地起兩個node服務,分別佔用3333和4444。父頁面是:
1.window-name/public/index.html

const proxy = function(url ,callback) {
                let status = 0;
                const iframe = document.createElement('iframe');

                iframe.src = url;

                iframe.onload = function(){
                    if(status === 1) {
                        callback(iframe.contentWindow.name);
                        destoryFrame();
                    } else if (status === 0) {
                        iframe.contentWindow.location = 'http://localhost:4444/proxy.html';
                        status = 1;
                    }
                }

                document.body.appendChild(iframe);
          };
         

          function destoryFrame() {
              iframe.contentWindow.document.write('');
              iframe.contentWindow.close();
              document.body.removeChild(iframe);
          }

          proxy('http://localhost:3333/iframe.html', function(data) {
              alert(data);
          })

2.iframe 頁面是
window-name/public/iframe.html

<script>
        window.name = 'this is window.name from iframe';
    </script>

3.還有一個代理頁面,跟父頁面同源。內容爲空就好。目錄:/window-name/public/proxy.html

總結:經過iframe的src屬性由外域轉向本地域,跨域數據即由iframe的window.name從外域傳遞到本地域。這個就巧妙地繞過了瀏覽器的跨域訪問限制,但同時它又是安全操做。

5、location.hash+iframe 跨域

原理:A域想和B域通訊,經過中間頁面c。不一樣域之間經過location.hash來通訊,而相同域之間直接經過js來通訊。

實現:A域:a.html ----> B域:b.html ----> A域:c.html,a與b不一樣域只能經過hash值單向通訊,b與c也不一樣域也只能單向通訊,但c與a同域,因此c可經過parent.parent訪問a頁面全部對象。

目錄:location-hash/public/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>
    <iframe id='iframe' src="http://localhost:4444/b.html" style="display:none;"></iframe>
</head>
<body>
    <script>
        const iframe = document.getElementById('iframe');

        //向b.html傳遞hash值
        setTimeout(function(){
           iframe.src = iframe.src + '#user=admin';
        },1000);

        function onCallback(res) {
            alert('data from c.html ---->' + res);
        }
    </script>
</body>
</html>

目錄:location-hash/public/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>b</title>
    <iframe id="iframe"  src="http://localhost:3333/c.html" style="display:none;"></iframe>
</head>
<body>
    <script>
        const iframe = document.getElementById('iframe');

        // 監聽a.html傳來的hash值,再傳給c.html
        window.onhashchange = function(){
            iframe.src = iframe.src + location.hash;
        }
    </script>
</body>
</html>

目錄:location-hash/public/c.html

<script>
        // 監聽b.html傳來的hash值
        window.onhashchange = function(){
            // 再經過操做同域a.html的js回調,將結果傳回
            window.parent.parent.onCallback('hello '+ location.hash.replace('#user=', ''));
        };
    </script>

而後,咱們經過node服務將a.html和c.html部署在同一個端口下,將b.html部署在另外一個端口。

//location-hash/server1.js
app.use('/a.html', express.static(__dirname+'/public/a.html'));
app.use('/c.html', express.static(__dirname+'/public/c.html'));
app.listen(3333);

//location-hash/server2.js
app.use('/b.html', express.static(__dirname+'/public/b.html'));
app.listen(4444);

最後,咱們分別將兩個服務跑起來。訪問localhost:3333能夠看到彈窗。

圖片描述

即b.html藉助c.html接收到了a.html發來的消息,並給予迴應"hello admin",跨域成功~

接下文--->「跨域」利用node.js實踐前端各類跨域方式(下)

相關文章
相關標籤/搜索