原文地址在這裏: http://goenning.net/2016/02/10/simple-server-side-cache-for-expressjs/
第一次翻譯,不是很好,但大概意思應該還可以。有錯誤,請多多指正。
express是我目前爲止見過擴展性最好的web框架。它的中間件結構使它可以輕鬆地以標準化方式添加額外的功能。
本次主題,我們將會討論一個很小很簡單,但功能非常強大、有用的中間件。它將會幫助你提升你的express Web程序的性能,而且無需任何依賴。
關於服務端緩存
無論是在 desktop, mobile or web哪一方面,Cache都常被我們用來提升程序性能。當處理web應用程序的時候,雖然可以使用當前所有瀏覽器都支持的響應頭來進行客戶端緩存,從而提升頁面加載效率。但當一個內容非常繁雜的頁面需要2s來進行HTML輸出的時候,即使啓用客戶端緩存該頁面,服務器仍然需要針對每一個來訪用戶進行頁面渲染。想想一個大型的新聞門戶網站首頁,難道他們要針對每一個用戶一遍又一遍地處理HTML嗎?
這時候服務器緩存就派上用場了。使用服務器緩存的目標是對相同的客戶端請求返回相同的內容。在上面的例子裏,第一個請求仍然需要2s處理HTML,但是接下來請求將會命中緩存,服務器可以在幾毫秒內發送響應內容。
有很多種方法可以實現服務器緩存,例如NGINX以及類似於CloudFlare的CDN。在這裏,我們將會使用nodejs和express來輕鬆簡便地實現它。
代碼展示
我們的目標是輕鬆實現服務器緩存。接下來,我們開始吧!
我們將充分利用 memory-cache npm模塊來將內容添加到緩存中。中間件如下:
var mcache = require('memory-cache');
var cache = (duration) => {
return (req, res, next) => {
let key = '__express__' + req.originalUrl || req.url
let cachedBody = mcache.get(key)
if (cachedBody) {
res.send(cachedBody)
return
} else {
res.sendResponse = res.send
res.send = (body) => {
mcache.put(key, body, duration * 1000);
res.sendResponse(body)
}
next()
}
}
}
它將把請求的url作爲key值進行緩存查詢。一旦查詢到緩存,將會直接發送響應報文。否則,就會在響應報文發送到客戶端之前,對響應進行緩存。然後調用下一個中間件。
這裏有一個非常簡單的例子——關於如何緩存一個繁重的處理頁面。
app.get('/', cache(10), (req, res) => {
setTimeout(() => {
res.render('index', { title: 'Hey', message: 'Hello there', date: new Date()})
}, 5000) //setTimeout was used to simulate a slow processing request
})
注意,上述路由包含兩個中間件。一個是關於緩存,另一個是真正用於處理請求的中間件。在這種情況下,當該路由收到第一次請求的時候,將不會立刻返回響應,而是會停留5s。但是在接下來的10s,連續的請求將會直接從緩存中得到響應,而不需要再去等待5s。有得必有失,該方法的缺點在你的響應報文中含有動態內容的時候將會展現出來。在上面的路由中,如果我們將當前時間作爲參數傳遞給視圖引擎,那麼緩存過期(10s)之前響應內容中都會含有相同的日期。
這裏非常棒的一點就是,以上方法適用於HTML,JSON,XML以及其他任何內容類型的響應。
你可以輕鬆地引入該中間件到已存在的站點中,來緩存任何你想要緩存的路由。
注意:不要緩存 GET 和 POST 方法
在這個例子中,我們使用了在內存中緩存內容的NPM模塊,這有利有弊。
- 在內存中緩存速度最快
- 使用簡單,不需要添加額外的依賴。
- 如果服務器或進程出現故障,緩存將會丟失
- 由於每個進程在自己的內存區間存儲緩存內容,所以Node.js的多個進程之間內存不共享。
要解決大部分問題的一個選擇是使用諸如 Redis 的分佈式緩存服務。它可以僅僅通過一個npm 模塊 express-redis-cache中間件來實現。
'use strict'
var express = require('express');
var app = express();
var mcache = require('memory-cache');
app.set('view engine', 'jade');
var cache = (duration) => {
return (req, res, next) => {
let key = '__express__' + req.originalUrl || req.url
let cachedBody = mcache.get(key)
if (cachedBody) {
res.send(cachedBody)
return
} else {
res.sendResponse = res.send
res.send = (body) => {
mcache.put(key, body, duration * 1000);
res.sendResponse(body)
}
next()
}
}
}
app.get('/', cache(10), (req, res) => {
setTimeout(() => {
res.render('index', { title: 'Hey', message: 'Hello there', date: new Date()})
}, 5000) //setTimeout was used to simulate a slow processing request
})
app.get('/user/:id', cache(10), (req, res) => {
setTimeout(() => {
if (req.params.id == 1) {
res.json({ id: 1, name: "John"})
} else if (req.params.id == 2) {
res.json({ id: 2, name: "Bob"})
} else if (req.params.id == 3) {
res.json({ id: 3, name: "Stuart"})
}
}, 3000) //setTimeout was used to simulate a slow processing request
})
app.use((req, res) => {
res.status(404).send('') //not found
})
app.listen(3000, function () {
console.log('Example app listening on port 3000!')
})
編程愉快