nodejs 方便了我們前端開發者進行一些服務端上的操作,可以進行無縫地銜接。像其他一些後端語言,如 php, golang, java 等,都需要一定的學習成本,而 nodejs 則就是為前端開發者定製的。
在 nodejs 中,提供了原生的 http 模組,我們可以利用 http 模組搞幾個常用的小工具,能夠大大方便我們的工作。
我在之前從 0 到 1 學習 node(二)之搭建 http 伺服器的文章中也講解過 http 模組,不過我們這裡主要是利用 http 模組來打造幾個使用的工具。
1. 一個簡單的 HTTP 服務
使用 nodejs 搭建 http 服務非常地簡單,只需要引入 http 模組即可:
// server.js
const http = require('http');
const ip = '127.0.0.1';
const port = 3000;
http
.createServer((req, res) => {
res.end('heelo world!');
})
.listen(port, ip);
console.log(`server has started at ${ip}:${port}`);
然後執行 server.js 檔案:
$ node server.js
在控制檯就會輸出:
server has started at 127.0.0.1:3000
然後在瀏覽器上訪問http://127.0.0.1:3000
,就能看到hello world!的輸出。
如果想在啟動時,通過引數指定 IP 或埠。那麼我們可以通過process.env
來獲取命令列中指定的引數:
// 通過process.env來獲取指定的引數
// 並設定兜底的資料
const ip = process.env.IP || '127.0.0.1';
const port = process.env.PORT || 3000;
在執行 sever.js 時,就可以通過引數指定 IP 和埠:
$ PORT=3030 node app.js
2. 一個有延遲的請求
在做開發和除錯過程中,經常需要考慮到一個請求或者圖片等載入很慢時,應該怎麼處理。
比如有使用者反饋某些圖片載入很慢,導致頁面看起來不正常。那麼我就應該針對圖片載入慢進行一些處理,可是怎麼模擬這個載入很慢的圖片呢?
很多現有的介面,都無法模擬出這種有特殊延遲的情況,這裡我們可以使用 http 模組來實現。
我們在第 1 個 http 伺服器的基礎上,進行改進。
res.end()方法會告知伺服器當前次響應結束,若沒有呼叫,則一直處於等待狀態。
我們要實現一個有延遲的響應,可以使用 setTimeout 延遲呼叫 end()方法即可。這裡先實現一個有延遲的介面。
http
.createServer((req, res) => {
const index = req.url.indexOf('?');
if (index >= 0) {
const query = req.url.slice(index);
const ss = new URLSearchParams(query);
const timeout = ss.get('timeout');
const type = ss.get('type');
if (timeout && Number(timeout)) {
return setTimeout(() => {
if (type === 'json') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ code: 0, msg: 'hello world' }));
} else if (type === 'image') {
// 輸出本地一個圖片
} else {
res.end(`delay ${timeout}ms response`);
}
}, Number(timeout));
}
}
res.end('hello world!');
})
.listen(port, ip);
想要延遲輸出圖片時,需要通過資料流的方式讀取本地的圖片,然後輸出到前端:
const stream = fs.createReadStream('./img/s.jpg');
const responseData = []; //儲存檔案流
if (stream) {
//判斷狀態
stream.on('data', function (chunk) {
responseData.push(chunk);
});
stream.on('end', function () {
const finalData = Buffer.concat(responseData);
// response.write();
res.writeHead(200, { 'Content-Type': 'image/jpg' });
res.end(finalData);
});
}
3. 實現介面的中轉代理
我們有時會遇到需要的介面存在跨域,或者是內網介面的問題,這時我們就需要通過一箇中間層,來對介面進行中轉代理,才能正常地訪問介面。
3.1 原生 http 模組來實現
實現介面代理時要注意亮點:
- 透傳,接收到的所有資料,根據需要儘量都傳給代理的介面,例如 cookie,引數等;
- 設定跨域頭,為了方便前端的訪問,我們需要在返回的頭部加上 3 個可以跨域的欄位;
跨域的方式有很多種,比如 jsonp 也是其中一種,但 cors 跨域是比較好的一種,前端可以有效地控制請求時間和取消請求。
在設定跨域頭Access-Control-Allow-Origin
時,這裡是不建議直接設定成*
。一方面是不安全,所有的域名都可以訪問;再有就是前端不會再傳送 cookie,無法進行一些登入態的校驗等。
在設定Access-Control-Allow-Origin
之前,我們要先校驗下 headers 中的 referer,如果為空或者不滿足白名單的要求,則可以直接返回 403。
const allowList = ['joke.qq.com', 'www.qq.com'];
if (!req.headers || !req.headers.referer) {
res.writeHead(403, 'forbidden');
res.end('403 forbidden');
return;
}
const { hostname } = new URL(req.headers.referer);
if (!allowList.includes(hostname)) {
res.writeHead(403, 'forbidden');
res.end('403 forbidden');
return;
}
滿足要求之後,需要將 referer 最後的斜槓/
去掉,否則會設定不成功。完成的程式碼樣例如下:
const http = require('http');
const https = require('https');
const ip = process.env.IP || '127.0.0.1';
const port = process.env.PORT || 3001;
http
.createServer((req, res) => {
const allowList = ['joke.qq.com', 'www.qq.com'];
if (!req.headers || !req.headers.referer || allow) {
res.writeHead(403, 'forbidden');
res.end('403 forbidden');
return;
}
console.log('發起請求', req.headers);
https
.get('https://www.v2ex.com/api/topics/latest.json', (response) => {
let data = '';
response.on('data', (chunk) => {
data += chunk;
});
response.on('end', () => {
res.setHeader('Access-Control-Allow-Origin', (req.headers.referer || '').replace(/\/$/, ''));
res.setHeader('Access-Control-Allow-Methods', 'GET, POST');
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type');
res.end(data);
});
})
.on('error', (e) => {
console.error(`請求遇到問題: ${e.message}`, e);
res.end('error');
});
})
.listen(port, ip);
console.log(`server has started at ${ip}:${port}`);
3.2 proxy 代理元件
若需要代理更多的介面,或者路徑是從前端傳過來的,我們自己倒是也可以實現,不過還有更方便的 proxy 代理元件了。
這裡我們用 http-proxy 元件來實現:
const http = require('http');
const httpProxy = require('http-proxy');
const ip = process.env.IP || '127.0.0.1';
const port = process.env.PORT || 3000;
const proxy = httpProxy.createProxyServer({
target: 'https://www.v2ex.com', // 代理的介面地址
changeOrigin: true,
});
http
.createServer((req, res) => {
// 設定跨域頭
res.setHeader('Access-Control-Allow-Origin', (req.headers.referer || '').replace(/\/$/, ''));
res.setHeader('Access-Control-Allow-Methods', 'GET, POST');
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type');
// 將請求和響應的物件傳給proxy
proxy.web(req, res);
})
.listen(port, ip);
然後前端直接按照路徑發起請求即可:
axios('http://localhost:3000/api/topics/latest.json').then(console.log).catch(console.error);
4. 模擬資料
前端在寫頁面邏輯時,經常要考慮到資料的各種情況,比如無資料時,長列表,各種長度的暱稱等。
無論是讀取配置的 json 檔案,還是用程式碼生成的資料,都不具有隨機性。
現在,我們可以利用 mockjs 來實現各種資料的模擬:
const http = require('http');
const Mock = require('mockjs');
const ip = process.env.IP || '127.0.0.1';
const port = process.env.PORT || 3000;
http
.createServer((req, res) => {
const result = Mock.mock({
code: 0,
msg: 'success',
'x-from': 'mock',
data: Mock.mock({
'rank|20': [
{
'no|+1': 1, // no 欄位從 1 開始自增
uin: () => Mock.Random.string(32), // 32 長度的隨機字串
nick: () => Mock.Random.string(1, 20), // 長度在 1-20 之間的隨機字串
face: () => Mock.Random.image('120x120'), // 120*120 的圖片
score: () => Mock.Random.integer(1, 2000), // 分數
},
],
}),
});
res.setHeader('Access-Control-Allow-Origin', (req.headers.referer || '').replace(/\/$/, ''));
res.setHeader('Access-Control-Allow-Methods', 'GET, POST');
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type');
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(result, null, 2));
})
.listen(port, ip);
生成的資料:
關於 Mockjs 更多的語法規則,可以訪問https://github.com/nuysoft/Mock/wiki/Getting-Started。
5. 總結
使用 nodejs 還可以實現更多的功能,這裡我們也僅僅是實現了其中簡單的幾個。基於我們的前端知識和業務需求,我們還能實現更多的小工具。
也歡迎您關注我的公眾號:“前端小茶館”