靜態網頁使用Node.js跨域代理服務

guard-wen發表於2019-04-12

1. 需求背景

公司網站的本地開發版之前一直都是部署在本地電腦上Tomcat容器裡的,好處就是本地搭建伺服器環境介面無需做跨域請求處理,壞處就是後臺程式碼的每次更新都需要拷貝一份至我的電腦覆蓋,並且本地環境與測試線環境資料仍然有所差異,在本地環境除錯不便。

前天在與新入職的Java工程師討論如何分工協作的時候聊到了部署Tomcat容器到我本地的壞處,然後仔細想想我最近不是在學nodejs嘛,為何不學以致用在我本地用nodejs部署跨域代理服務呢?於是花了兩天零碎的時間研究瞭如何使用nodejs來實現靜態頁面的介面請求代理。 在實現跨域之前先理清楚我想實現的什麼樣的功能。

2. 跨域的幾種方式

靜態網站的裡的ajax請求是相對路徑,同一套程式碼要在本地開發環境、測試線環境、大陸正式線、香港正式線部署。所以請求不適合使用絕對路徑。

前端跨域

前端跨域的方式有兩種

  1. 通過script標籤跨域
  2. 通過jsonp跨域

兩者的原理都是使用請求靜態資原始檔的方式,用回撥函式的包裝把json資料返回前端。從而騙過瀏覽器的跨域審查。使用這種方法來跨域意味著前端和後端的程式碼都要根據跨域的要求來重構一番,並且只能傳送GET請求,這樣的方式肯定是不可行的,因為只有在本地開發時才需要跨域,程式碼部署到線上伺服器後就不需要跨域了。 所以開始研究後端方案跨域。也有兩種。

後端跨域

  1. CORS跨域資源共享
  2. 跨域代理服務

CORS (Cross-Origin Resource Sharing)跨域資源共享,它允許瀏覽器向跨源(協議 + 域名 + 埠)伺服器,發出XMLHttpRequest請求,從而克服了AJAX只能同源使用的限制。CORS需要瀏覽器和伺服器同時支援,實現CORS通訊的關鍵是伺服器。只要伺服器實現了CORS介面,就可以跨源通訊。

使用CORS來實現跨域仍然需要在目標伺服器的介面上配置跨域請求引數,所以實現起來要前後端配合還是比較麻煩的。而且僅僅是測試線伺服器需要跨域,正式線伺服器禁止跨域,這樣子修改後,後臺程式碼就會出現不一致。所以此方法也棄用。

跨域代理服務,原理是把前端http請求傳送給後臺的代理,讓代理代為轉發請求,從而不需要在瀏覽器上進行跨域請求。代理獲取到響應結果再轉發到前端。使用它的好處是目標伺服器上的介面程式碼和前端程式碼都不需要做任何更改,只需開啟本地跨域代理服務即可。

每種跨域方式分析過後,覺得最可行的操作就是使用靜態資源代理服務來做前端跨域請求。

3. 使用nodejs跨域代理服務

在nginx和nodejs間選擇了後者,於是開始了在本地搭建http伺服器和尋找合適的代理跨域中介軟體。

搭建了本地http服務後,具體怎麼操作實現代理跨域其實還是沒多大思路的,於是呢就開始百度尋找案例來參考,瞭解了使用nodejs中介軟體跨域的大概思路。

先是搭建起http伺服器,對訪問主機地址的網路請求進行判斷,如果是api介面的請求時則使用中介軟體的代理服務進行轉發。讓中介軟體代為向目標伺服器發起請求,響應結果再轉發至前端。

4. 程式碼實現

檔案結構目錄大概如此,ROOT資料夾為網頁資料夾

靜態網頁使用Node.js跨域代理服務

1.建立專案資料夾並npm初始化資料夾

mkdir demo
cd demo
npm init -y
複製程式碼

2.安裝node-http-proxy中介軟體 npm install http-proxy --save-dev

3.建立啟動檔案proxy.js

var PORT = 3000;//定義埠號
var tatgetPATH='http://www.a.com/'//目標伺服器地址
var http = require('http'); //引入http模組
var url=require('url');  //引入url模組
var fs=require('fs');  //引入檔案模組
var mine=require('./fileFormat').types;  //檔案格式字典
var path=require('path'); //引入path模組
var httpProxy = require('http-proxy');  //跨域代理中介軟體

var proxy = httpProxy.createProxyServer({
    target: tatgetPATH,   //介面地址
    // 下面的設定用於https
    // ssl: {
    //     key: fs.readFileSync('server_decrypt.key', 'utf8'),
    //     cert: fs.readFileSync('server.crt', 'utf8')
    // },
    // secure: false
});

var server = http.createServer(function (request, response) {
    var pathname = url.parse(request.url).pathname;

    //訪問根目錄時改為指向首頁檔案
    if(pathname=='/'){
        pathname='index.html' 
    }
     // 指定根目錄
    var realPath = path.join("./ROOT", pathname);

    var ext = path.extname(realPath);
    ext = ext ? ext.slice(1) : 'unknown';

    //判斷如果是api介面訪問,則通過proxy轉發
    if(pathname.indexOf("./ROOT") > 0){
        // console.log('發起請求:',pathname)
        proxy.web(request, response);
        return;
    }

    fs.exists(realPath, function (exists) {
        if (!exists) {
            response.writeHead(404, {
                'Content-Type': 'text/plain'
            });

            response.write("This request URL " + pathname + " was not found on this server.");
            response.end();
        } else {
            fs.readFile(realPath, "binary", function (err, file) {
                if (err) {
                    response.writeHead(500, {
                        'Content-Type': 'text/plain'
                    });
                    response.end(err);
                } else {
                    var contentType = mine[ext] || "text/plain";
                    response.writeHead(200, {
                        'Content-Type': contentType
                    });
                    response.write(file, "binary");
                    response.end();
                }
            });
        }
    });
});
server.listen(PORT);

//代理服務執行錯誤的監聽
proxy.on('error', function(err, req, res){
    res.writeHead(500, {
        'content-type': 'text/plain'
    });
    console.log(err);
    res.end('Something went wrong. And we are reporting a custom error message.');
});
console.log("Server runing at port: " + PORT + "."+tatgetPATH);
複製程式碼

引入檔案格式字典 fileFormat.js

exports.types = {
    "css": "text/css",
    "gif": "image/gif",
    "html": "text/html",
    "ico": "image/x-icon",
    "jpeg": "image/jpeg",
    "jpg": "image/jpeg",
    "js": "text/javascript",
    "json": "application/json",
    "pdf": "application/pdf",
    "png": "image/png",
    "svg": "image/svg+xml",
    "swf": "application/x-shockwave-flash",
    "tiff": "image/tiff",
    "txt": "text/plain",
    "wav": "audio/x-wav",
    "wma": "audio/x-ms-wma",
    "wmv": "video/x-ms-wmv",
    "xml": "text/xml",
    "woff": "application/x-woff",
    "woff2": "application/x-woff2",
    "tff": "application/x-font-truetype",
    "otf": "application/x-font-opentype",
    "eot": "application/vnd.ms-fontobject"
  };
複製程式碼

程式碼寫完後執行proxy.js即可通過跨域代理服務來實現本地呼叫不同域伺服器的介面了。

5. 總結

跨域的解決方案有很多,具體選擇哪一種方案還是根據實際的情況來進行分析。

以前還沒學習nodejs的時候,遇到跨域問題能想到的解決方法就是使用ajax的jsonp,需要後端返回的介面加上回撥函式,前端再通過通過函式對資料進行接收。改起來十分之麻煩。

前端若是懂得後端的一些知識,便能夠使用更多的解決方案來解決問題。web開發中包括了前端和後端,能夠熟練使用兩端的技能,才能夠在web開發中遊刃有餘。

相關文章