Node實(shí)現(xiàn)前端本地開發(fā)接口代理服務(wù)
背景
我們在前端開發(fā)的接口聯(lián)調(diào)階段,經(jīng)常會遇到跨域問題,因?yàn)楸镜赝ǔJ褂?code>localhost域名來啟動項(xiàng)目,當(dāng)然我們可以通過配置host解決這個問題,但當(dāng)需要訪問線上頁面的時(shí)候,又必須關(guān)閉host;我們也可以通過webpack的插件dev-server來配置代理(Proxy),但有時(shí)需要和多個服務(wù)端研發(fā)進(jìn)行接口聯(lián)調(diào),因此就需要配置多個Proxy,設(shè)置多個虛擬接口前綴,還要考慮發(fā)布后線上如何不走代理,整體來說,配置工作是比較麻煩的。
目標(biāo)
針對以上背景,我們需要一個更加簡單靈活的接口代理方案,我們的目標(biāo)是:
- 接入簡單,只需在前端項(xiàng)目中增加少量代碼;
- 環(huán)境隔離,只針對本地開發(fā)環(huán)境使用代理,對線上無影像,發(fā)布時(shí)也無需修改代碼;
- 配置靈活:可針對不同接口進(jìn)行差異化配置,可同時(shí)對接多個服務(wù)端研發(fā)進(jìn)行聯(lián)調(diào);
方案
我們在使用webpack的插件dev-server時(shí),本質(zhì)是本地運(yùn)行了一個代理服務(wù),前端頁面發(fā)送的網(wǎng)絡(luò)請求,實(shí)際都是請求了這個代理服務(wù),再由代理服務(wù)轉(zhuǎn)發(fā)到實(shí)際的接口URL上,最后代理服務(wù)再將接口返回的數(shù)據(jù)透傳給前端頁面。
根據(jù)這個原理,我們來自行搭建一個代理服務(wù),也將它在本地運(yùn)行,來實(shí)現(xiàn)同dev-server插件一樣的代理過程,但我們會在本方案中引入更加靈活的一種配置方式,也就是通過修改config.json配置文件,來實(shí)現(xiàn)更加簡化的配置,以及隨用隨改的靈活特性。
實(shí)施
創(chuàng)建NodeJS項(xiàng)目
首先在本地創(chuàng)建一個NodeJS項(xiàng)目(需要安裝NodeJS環(huán)境,版本建議12+),命名為api-proxy-server,具體步驟略。
溫馨提示:本文的代理服務(wù)項(xiàng)目源代碼,筆者免費(fèi)提供!只需 點(diǎn)贊+關(guān)注 即可在評論區(qū)留言索要,留下您的郵箱,筆者會在看到的第一時(shí)間發(fā)送,項(xiàng)目源代碼README文件詳細(xì)描述了如何使用,方便您快速接入。
編寫Express服務(wù)
我們的代理服務(wù)使用Express框架來開發(fā)(需要安裝依賴),也可以根據(jù)個人習(xí)慣使用其他框架,比如KOA、Egg.js等。
首先在項(xiàng)目主文件中導(dǎo)入相關(guān)依賴:
const express = require('express');
const http = require('http');
const bodyParser = require('body-parser');
const app = express();然后使用bodyParser對請求數(shù)據(jù)進(jìn)行解析:
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());接下來這一步很關(guān)鍵,就是要允許跨域,我們要允許所有域名和請求的訪問,具體設(shè)置如下:
app.all('*', (req, res, next) => {
res.header('Access-Control-Allow-Origin', req.headers.origin);
res.header('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With');
res.header('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTION');
res.header('Access-Control-Allow-Credentials', 'true');
res.header('X-Powered-By', '3.2.1');
next();
});然后我們給這個服務(wù)編寫默認(rèn)的路由(也可以理解為接口,對于前端來說,服務(wù)端提供的路由都可以視為接口),默認(rèn)的路由我們定義為GET請求,方便我們在瀏覽器直接訪問,來驗(yàn)證服務(wù)是否成功啟動。
Express掛載路由的方式如下:
app.get('/', (req, res) => {
res.send('Hello, Welcome use api proxy.');
});最后,我們來編寫啟動服務(wù)的代碼,如下:
const port = 8080;
http.createServer(app).listen(port);
console.log(`http://127.0.0.1:${port} 服務(wù)已啟動`);按照以上步驟編寫完代碼后,啟動服務(wù),在瀏覽器中訪問http://127.0.0.1:8080,如果看到瀏覽器顯示“Hello, Welcome use api proxy.”說起服務(wù)啟動成功了。
關(guān)于如何啟動服務(wù),筆者的做法是,在package.json中配置啟動腳本dev,然后運(yùn)行npm run dev即可,也可以通過pm2進(jìn)程守衛(wèi)插件(需要安裝依賴)來啟動服務(wù),這樣做的好處是可以在程序報(bào)錯時(shí),保證服務(wù)不會停止。
啟動腳本配置如下:
"scripts": {
"start": "pm2 start ./src/server.js",
"stop": "pm2 stop ./src/server.js",
"dev": "node ./src/server.js"
},編寫代理接口
在上述步驟中,我們已經(jīng)可以啟動代理服務(wù),并編寫了一個默認(rèn)的GET路由(接口),接下來我們來實(shí)現(xiàn)本文的核心內(nèi)容,編寫一個POST類型的接口,用于對前端接口請求進(jìn)行代理。
為了方便后續(xù)的維護(hù)和擴(kuò)展,我們將代理接口的源代碼編寫到一個獨(dú)立的文件中,目錄為src/routes/api,文件名為index.js,然后在主文件中導(dǎo)入并掛載路由即可,代碼如下:
const api = require('./routes/api');
// ......
app.post('/api', api);注意:代理接口的路由我們定義為/api,因此在前端項(xiàng)目中調(diào)用這個服務(wù)的完整路徑就是http://127.0.0.1:8080/api,這個接口只能通過axios來訪問,因?yàn)樗?code>POST類型的,不支持瀏覽器訪問驗(yàn)證。
我們打開src/routes/api/index.js文件來編寫具體的代碼,首先導(dǎo)出一個基本的路由函數(shù):
const axios = require("axios");
const qs = require("qs");
const fs = require("fs");
const fspath = require("path");
module.exports = (req, res) => {
// ......
}在這個函數(shù)內(nèi),我們來處理前端請求,先從請求體中獲取參數(shù):
const { path, params } = req.body;其中path指定了要訪問哪個服務(wù)端接口路徑,params是要透傳給服務(wù)端的實(shí)際入?yún)ⅰ?/p>
然后我們在src/routes/api/目錄下,創(chuàng)建一個接口配置文件config.json,這個配置文件維護(hù)了默認(rèn)的服務(wù)端接口域名或IP地址,以及默認(rèn)的請求方式和數(shù)據(jù)類型。具體定義如下:
| 屬性名 | 說明 | 示例 |
|---|---|---|
| baseUrl | 服務(wù)端接口默認(rèn)地址 | http://192.168.1.17 |
| method | 請求方式,默認(rèn)是POST | GET / POST |
| contentType | 數(shù)據(jù)類型,默認(rèn)是json | form / json |
繼續(xù)編寫代碼,來讀取這個config.json配置文件,代碼如下:
// 讀取接口配置文件
const configPath = fspath.resolve(process.cwd(), './src/routes/api/config.json');
if (!fs.existsSync(configPath)) {
res.json({ code: '-1', msg: '接口配置文件不存在!' });
return;
}
const configStr = fs.readFileSync(configPath, { encoding: 'utf-8' });
let config = null;
try {
config = JSON.parse(configStr);
} catch (error) {
res.json({ code: '-1', msg: '接口配置文件格式有誤,解析失??!' });
return;
}
// 獲取接口默認(rèn)配置
let { baseUrl, method='POST', contentType='json', extra=[] } = config;代碼寫到這里,我們已經(jīng)拿到了真實(shí)的服務(wù)端接口地址、請求類型以及數(shù)據(jù)類型,然后就可以通過axios來發(fā)送請求了,但這樣并不支持個別請求的差異化,比如有的接口需要跟另外的服務(wù)端研發(fā)聯(lián)調(diào),也就是baseUrl不同、有的接口請求類型是GET,有的接口請求的數(shù)據(jù)類型是application/x-www-form-urlencoded,因此我們需要一種機(jī)制來實(shí)現(xiàn)對差異化接口的配置,我們繼續(xù)改寫config.json文件,加入extra數(shù)組,在這個數(shù)組中,我們放入一組對象,來定義個別接口的差異化屬性,具體定義如下:
| 屬性名 | 說明 | 示例 |
|---|---|---|
| path | 接口路徑(必填),需要差異化配置的接口 | /user/permision |
| baseUrl | 服務(wù)端接口地址(選填) | http://192.168.1.16 |
| method | 請求方式(選填) | GET / POST |
| contentType | 數(shù)據(jù)類型(選填) | form / json |
有了以上配置,我們就可以遍歷extra數(shù)組,如果當(dāng)前接口請求的path在其中,就需要按照差異化的配置發(fā)送請求,具體代碼如下:
// 獲取額外的接口配置
if (extra.length > 0) {
const extraCfg = extra.find(item => item.path === path);
if (extraCfg) {
baseUrl = extraCfg.baseUrl || baseUrl;
method = extraCfg.method || method;
contentType = extraCfg.contentType || contentType;
}
}最后我們使用axios來發(fā)送請求到實(shí)際的服務(wù)端接口地址,然后將返回的數(shù)據(jù)下發(fā)給前端頁面即可。代碼如下:
const url = baseUrl + path;
const headers = {};
if (contentType === 'form') {
headers['Content-Type'] = 'application/x-www-form-urlencoded';
}
axios({
method,
url,
headers,
timeout: 3000,
data: contentType === 'form' ? qs.stringify(params) : params
}).then(result => {
res.json(result.data);
Log.api(url, params, result.data);
}).catch(error => {
res.json({ code: '-1', msg: '網(wǎng)絡(luò)錯誤' });
Log.api(url, params, '網(wǎng)絡(luò)錯誤');
});代碼中的Log是用來記錄日志的,方便我們追蹤代理服務(wù)的運(yùn)行情況,具體實(shí)現(xiàn)可查看本項(xiàng)目源代碼。
至此我們的代理服務(wù)就開發(fā)完成了,運(yùn)行這個服務(wù),我們就可以在前端項(xiàng)目中接入并使用它了。**注意:**當(dāng)我們修改了config.json文件后,保存即可,不需要重啟服務(wù)。如果電腦關(guān)機(jī)重啟了,則需要手動啟動代理服務(wù)。
前端項(xiàng)目接入
在前端項(xiàng)目封裝axios請求的地方,判斷如果是本地開發(fā)環(huán)境,則將URL指向此服務(wù),并對入?yún)⑦M(jìn)行簡單包裝即可。由于我們只對本地環(huán)境做了接口代理,因此發(fā)布項(xiàng)目時(shí)無需任何修改,發(fā)布后的版本仍會調(diào)用實(shí)際的服務(wù)端接口。
代碼示例:
let baseUrl = 'http://api.xyz.com'; // 線上接口域名
let data = { a: 1 } // 接口入?yún)?
let path = '/user/permision'; // 實(shí)際的接口路徑
let url = baseUrl + path; // 請求的完整接口url
if (process.env.NODE_ENV === 'development') {
url = 'http://127.0.0.1:8080/api';
data = { path, params: data }
}
// 發(fā)送請求
axios({ url, data })以上就是實(shí)現(xiàn)前端本地開發(fā)接口代理服務(wù)的完整思路和過程,更多相關(guān)Node 本地開發(fā)接口代理 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
nodejs實(shí)現(xiàn)一個自定義的require方法的詳細(xì)流程
大家對nodejs中的require方法應(yīng)該不會陌生,這個方法可以用來導(dǎo)入nodejs的內(nèi)置模塊,自定義模塊,第三方模塊等,使用頻率非常高,那么這個方法內(nèi)部是如何實(shí)現(xiàn)的呢?本篇文章就是從頭到尾拆分實(shí)現(xiàn)流程,最終實(shí)現(xiàn)一個自定義的require方法,需要的朋友可以參考下2025-03-03
Vue+Node服務(wù)器查詢Mongo數(shù)據(jù)庫及頁面數(shù)據(jù)傳遞操作實(shí)例分析
這篇文章主要介紹了Vue+Node服務(wù)器查詢Mongo數(shù)據(jù)庫及頁面數(shù)據(jù)傳遞操作,結(jié)合實(shí)例形式分析了node.js查詢MongoDB數(shù)據(jù)庫及vue前臺頁面渲染等相關(guān)操作技巧,需要的朋友可以參考下2019-12-12
Node.js使用officecrypto-tool實(shí)現(xiàn)讀取加密的Excel和Word文檔
這篇文章主要為大家詳細(xì)介紹了Node.js如何使用officecrypto-tool實(shí)現(xiàn)讀取加密的Excel和Word文檔的功能,感興趣的小伙伴可以跟隨小編一起了解一下2023-09-09
json對象及數(shù)組鍵值的深度大小寫轉(zhuǎn)換問題詳解
這篇文章主要給大家介紹了關(guān)于json對象及數(shù)組鍵值的深度大小寫轉(zhuǎn)換問題的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2018-03-03
node.JS事件機(jī)制與events事件模塊的使用方法詳解
本文將詳細(xì)介紹nodeJS事件機(jī)制與events事件模塊的使用方2020-02-02

