【小哥哥, 跨域要不要了解下】ServerProxy
系列文章:
- ofollow,noindex">【小哥哥, 跨域要不要了解下】JSONP
- 【小哥哥, 跨域要不要了解下】CORS 基礎篇
- 【小哥哥, 跨域要不要了解下】CORS 進階篇
- 【小哥哥, 跨域要不要了解下】NGINX 反向代理
- 【小哥哥, 跨域要不要了解下】ServerProxy
在系列文章的第一篇我們談到過跨域問題產生的原因是 瀏覽器 的同源策略. 那麼伺服器之間通訊就不會受到相關條件的限制. 那麼是不是我們可以通過同域伺服器幫助訪問其他域名的 api 呢? 如果可以的話, 那豈不是可以想訪問誰就訪問誰? 限制, 不存在的...

ps: 本文涉及到部分後端知識, 需要有一丟丟的 nodejs
koa
基礎. 主要用於搭建一個 web
伺服器, 當然沒有基礎也沒啥關係, 先去node koa 官網看看. 回不回來???
隨你咯 :smile:
建立專案目錄
繼續上一步, 本文只會建立一個後端專案. 所以不需要在 ./fe
目錄下建立前端專案啦, 專案目錄如下.

其中, serverProxy
目錄是專案的主目錄. www
目錄即為前端靜態檔案的託管目錄. base.js
為後端主程式, add.js subtract.js
分別表示兩個第三方服務, 分別提供了計算加法和減法的能力.
安裝 koa
- 首先執行
cd be/serverProxy
將路徑切換到serverProxy
- 執行
npm init -y
初始化為一個 node 專案 - 執行
npm i koa -S
完成koa
的安裝
驗證 koa
安裝是否完成
- 編輯
base.js
寫入以下內容
const Koa = require('koa'); const app = new Koa(); const PORT = 1234; app.use((ctx) => { ctx.body = 'Hello World'; }); app.listen(PORT, () => { console.log('the server is listen: ', PORT); }); 複製程式碼
- 完成後執行
node base.js
看到命令列中輸出了the server is listen: 1234
說明啟動成功 - 瀏覽器訪問localhost

此時 程式碼
引入 koa-static
模組
在之前文章中, 我們總是要通過 live-server
啟動一個本地的靜態資源服務. 用於託管前端靜態檔案. koa
生態中有現成的中介軟體 koa-static
可以提供直接在後端專案中建立靜態資源服務的能力.
- 首先執行
npm i koa-static -S
安裝koa-static
- 調整
base.js
const Koa = require('koa'); // 引入 koa-static const koaStatic = require('koa-static'); const app = new Koa(); const PORT = 1234; // 使用 koa-static 中介軟體, 並指定靜態檔案目錄為 www app.use(koaStatic('./www')); app.use((ctx) => { console.log(ctx.req.url); ctx.body = 'Hello World'; }); app.listen(PORT, () => { console.log('the server is listen: ', PORT); }); 複製程式碼
- 編寫前端
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>ServerProxy 實現跨域</title> </head> <body> ServerProxy 實現跨域 </body> </html> 複製程式碼
- 執行
node base.js
重啟專案 - 瀏覽器開啟 localhost:1234/index.html

之前準備的 html 頁面赫然在目 :smile:. 至此, 靜態檔案服務就搭建成功了(相當於我們自己實現了一個 live-server
) 程式碼地址
通過 ajax 訪問當前後端介面
通過 koa-static
中介軟體, 我們搭建了一個自己的靜態檔案伺服器. 接下來演示一個不跨域的請求...

- 首先修改後端程式碼
const Koa = require('koa'); const koaStatic = require('koa-static'); const app = new Koa(); const PORT = 1234; app.use(koaStatic('./www')); app.use((ctx) => { let ret; // 獲取本次接收的請求的請求路徑 const path = ctx.req.url; // 如果請求路徑以api開頭, 那麼作為介面請求處理 if (path.startsWith('/api')) { // 這樣實現的路由不是很優雅, 但是能用 :joy: switch (path) { case '/api/getFriend': ret = { name: 'quanquan', friend: 'gl' }; break; default: ret = { errno: 1, errmsg: '未知介面' }; break; } } ctx.body = ret; }); app.listen(PORT, () => { console.log('the server is listen: ', PORT); }); 複製程式碼
上述程式碼中定義了 /api/getFriend
介面, 通過瀏覽器訪問的如下圖:

node base.js
重啟後端專案
接下來修改前端程式碼. 通過 ajax 的方式訪問該介面
修改前端程式碼:
<!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>ServerProxy 實現跨域</title> </head> <body> ServerProxy 實現跨域 <script> // 一個常規的 ajax, 感興趣的兄弟們也看看. 手寫 ajax 好多面試官還在考 var xhr = new XMLHttpRequest() xhr.open('GET', '/api/getFriend') xhr.onreadystatechange = function() { if(xhr.readyState === 4 && xhr.status === 200) { console.log('介面返回的資料為: ', xhr.responseText) } } xhr.send() </script> </body> </html> 複製程式碼
重新整理瀏覽器, 控制檯展示如下. 沒有報錯, 返回的資訊前端直接拿到了.

原來前後端同域時資料互動這麼的簡單.

前後端跑通階段 程式碼
完善第三方服務
專案開發中經常會用到一些基礎服務, 比如天氣資訊, 地理位置資訊等等. 這些服務能力一般是通過呼叫第三方的介面來實現的(你開發一個網站, 先發射一顆氣象衛星到天上也不太現實). 這一步我們建立兩個第三方服務, 分別提供加法和減法運算.
加法運算服務 add.js
const Koa = require('koa'); const app = new Koa(); const PORT = 1111; app.use((ctx) => { // 獲取引數 const { a, b } = ctx.query; // 嘗試將引數轉化為數字後進行加法操作 const result = Number(a) + Number(b); ctx.body = { result }; }); app.listen(PORT, () => { console.log('the server is listen: ', PORT); }); 複製程式碼
執行命令 node add.js
啟動程式, 然後瀏覽器端訪問localhost得到的結果如下, 說明加法計算服務啟動成功.

減法運算服務 subtract.js
const Koa = require('koa'); const app = new Koa(); const PORT = 2222; app.use((ctx) => { // 獲取引數 const { a, b } = ctx.query; // 嘗試將引數轉化為數字後進行減法操作 const result = Number(a) - Number(b); ctx.body = { result }; }); app.listen(PORT, () => { console.log('the server is listen: ', PORT); }); 複製程式碼
執行命令 node subtract.js
啟動程式, 然後瀏覽器端訪問localhost得到的結果如下, 說明減法計算服務啟動成功.

目前 程式碼
通過後端代理訪問第三方服務
建立完加法和減法服務, 我們還是有僥倖心理忍不住在前端專案裡訪問一下試試, 萬一能通了呢? 就不用費事兒研究跨域了, 嘗試一下

修改前端程式碼中的介面地址 xhr.open('GET', 'http://localhost:1111/?a=1&b=2')
完整程式碼 , 之後直接重新整理瀏覽器(請思考, 為什麼修改了 js 檔案需要執行 node ...
重啟服務, 而修改了 html 檔案只需要重新整理瀏覽器就可以了呢?).

還是之前的報錯, 還是熟悉的味道. 不好使...
回想一下之前的思路. 瀏覽器有同源策略的限制伺服器沒有. 我們的前端專案託管在後端專案中所以訪問我們自己的後端不跨域. 我們的後端請求第三方服務沒有限制. 那麼 ^_^

npm i axios -S
base.js
const Koa = require('koa'); const koaStatic = require('koa-static'); const axios = require('axios'); const app = new Koa(); const PORT = 1234; app.use(koaStatic('./www')); app.use(async (ctx) => { let ret; // 獲取本次接收的請求的請求路徑 const path = ctx.req.url.split('?')[0]; console.log('ctx.query.server', ctx.query.server); // 如果請求路徑以api開頭, 那麼作為介面請求處理 if (path.startsWith('/api')) { // 這樣實現的路由不是很優雅, 但是能用 :joy: switch (path) { case '/api/getFriend': ret = { name: 'quanquan', friend: 'gl' }; break; // 如果介面需要代理介面路徑為 /api/proxy case '/api/proxy': // axios 直接訪問前端給出的目標伺服器url, 並將目標伺服器返回的資料直接返回給前端 ret = (await axios.get(ctx.query.server)).data; break; default: ret = { errno: 1, errmsg: '未知介面' }; break; } } ctx.body = ret; }); app.listen(PORT, () => { console.log('the server is listen: ', PORT); }); 複製程式碼
前端程式碼:
<!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>ServerProxy 實現跨域</title> </head> <body> <h3>ServerProxy 實現跨域</h3> a: <input type="text" id="a" value="1"> b: <input type="text" id="b" value="2"> <button id="add">計算加法</button> <button id="subtrnct">計算減法</button> <div>計算結果為: <span id="ret"></span></div> <script> var aDom = document.getElementById('a') var bDom = document.getElementById('b') var addBtn = document.getElementById('add') var subtrnctDom = document.getElementById('subtrnct') var retDom = document.getElementById('ret') function add() { if(!a.value.trim() || !b.value.trim()) return var xhr = new XMLHttpRequest() xhr.open('GET', '/api/proxy' + '?server=' + encodeURIComponent('http://localhost:1111/?a='+ a.value +'&b=' + b.value)) xhr.onreadystatechange = function() { if(xhr.readyState === 4 && xhr.status === 200) { console.log('介面返回的資料為: ', xhr.responseText) retDom.innerHTML = JSON.parse(xhr.responseText).result } } xhr.send() } function subtrnct() { if(!a.value.trim() || !b.value.trim()) return var xhr = new XMLHttpRequest() xhr.open('GET', '/api/proxy' + '?server=' + encodeURIComponent('http://localhost:2222/?a='+ a.value +'&b=' + b.value)) xhr.onreadystatechange = function() { if(xhr.readyState === 4 && xhr.status === 200) { console.log('介面返回的資料為: ', xhr.responseText) retDom.innerHTML = JSON.parse(xhr.responseText).result } } xhr.send() } addBtn.addEventListener('click', add) subtrnctDom.addEventListener('click', subtrnct) </script> </body> </html> 複製程式碼
最後結果:

結語: ServerProxy 的原理大概就是這個樣子的啦, 通過 ajax 訪問同域後端服務, 後端服務訪問目標服務並將目標服務返回的內容透傳給前端. 當然實際操作起來不會像例子這麼簡單. 我的另一個系列文章 【手把手帶你擼一個介面測試工具】 將會詳細介紹複雜一些的情況, 包括不同的請求型別, 請求頭設定以及響應頭獲取等等. 希望感興趣的小夥伴繼續關注.
關於跨域的其他方式:document.domain 一行程式碼可以搞定, 適合同主域名不同子域名的情況.postMessage 需要新增額外 iframe, 整體實現較為簡單, 一個 API 搞定, 感興趣的同學可以看看文件. 還有一些比較小眾的做法 flash
CSST
前端打點嚐嚐用到的 img 標籤等等, 這裡就不一一列舉了, 學無止境...
