靜態(tài)服務(wù)器(無(wú)緩存無(wú)更新請(qǐng)求數(shù)據(jù))
- 優(yōu)點(diǎn):簡(jiǎn)單。
- 缺點(diǎn):每次請(qǐng)求必須查找返回原始文件,浪費(fèi)帶寬。
有緩存-無(wú)更新請(qǐng)求數(shù)據(jù)(瀏覽器本地緩存)
- 優(yōu)點(diǎn):節(jié)省資源,速度快。
- 缺點(diǎn):服務(wù)器緩存中的數(shù)據(jù)變了,瀏覽器不知道數(shù)據(jù)是否發(fā)生改變。
緩存作用:
緩存是指代理服務(wù)器或客戶端本地磁盤內(nèi)保存的資源副本,利用緩存可減少對(duì)源服務(wù)器的訪問(wèn),可以節(jié)省通信流量和通信時(shí)間。
有緩存有更新請(qǐng)求數(shù)據(jù)
- 主要原理:請(qǐng)求被響應(yīng)的時(shí)候,響應(yīng)報(bào)文中有一個(gè)Expires :Mon,10 Dec 1990 02:25:22GMT(過(guò)期時(shí)間),再一次進(jìn)行請(qǐng)求的時(shí)候,用本地的時(shí)間與過(guò)期時(shí)間進(jìn)行比較,如果本地時(shí)間小于過(guò)期時(shí)間,那么從緩存中獲取,如果本地時(shí)間大于過(guò)期時(shí)間,重新向服務(wù)器發(fā)送請(qǐng)求獲取,再一次發(fā)送新的過(guò)期時(shí)間。
- 優(yōu)點(diǎn):緩存可控制。
- 缺點(diǎn):控制的功能太單一,這種格式的時(shí)間和容易寫錯(cuò)。
有緩存+更新機(jī)制升級(jí)版
Cache-Control: max-age=300;
以上代碼代表時(shí)間間隔,如果再一次的請(qǐng)求在時(shí)間間隔300s之內(nèi),就在緩存中獲取,否則從服務(wù)器獲取。
- Cache-Control還有其他值:
-
Public表示響應(yīng)可被任何中間節(jié)點(diǎn)緩存,如 Browser <-- proxy1 <-- proxy2 <-- Server,中間的proxy可以緩存資源,比如下次再請(qǐng)求同一資源proxy1直接把自己緩存的東西給 Browser 而不再向proxy2要。 -
Private表示中間節(jié)點(diǎn)不允許緩存,對(duì)于Browser <-- proxy1 <-- proxy2 <-- Server,proxy 會(huì)老老實(shí)實(shí)把Server 返回的數(shù)據(jù)發(fā)送給proxy1,自己不緩存任何數(shù)據(jù)。當(dāng)下次Browser再次請(qǐng)求時(shí)proxy會(huì)做好請(qǐng)求轉(zhuǎn)發(fā)而不是自作主張給自己緩存的數(shù)據(jù)。 -
no-cache表示不使用 Cache-Control的緩存控制方式做前置驗(yàn)證,而是使用 Etag 或者Last-Modified字段來(lái)控制緩存。不走緩存,響應(yīng)報(bào)文,是服務(wù)器發(fā)給瀏覽器的。瀏覽器在一次發(fā)送請(qǐng)求時(shí),發(fā)現(xiàn)這個(gè)字段,就不會(huì)再緩存中獲取數(shù)據(jù)了,而是再一次向服務(wù)器發(fā)送請(qǐng)求。 緩存只是本地緩存,而不是服務(wù)器對(duì)應(yīng)的緩存。報(bào)文會(huì)緩存,但是不會(huì)使用。 -
no-store,真正的不緩存任何東西。瀏覽器會(huì)直接向服務(wù)器請(qǐng)求原始文件,并且請(qǐng)求中不附帶 Etag 參數(shù)(服務(wù)器認(rèn)為是新請(qǐng)求)。不存,所有的流程都不進(jìn)行緩存。連報(bào)文都不會(huì)進(jìn)行緩存,啥都不緩存。 -
max-age,表示當(dāng)前資源的有效時(shí)間,單位為秒。
-
- 優(yōu)點(diǎn): 緩存控制功能更強(qiáng)大
- 缺點(diǎn): 不夠完美,超過(guò)時(shí)間間隔再向服務(wù)器要文件的時(shí)候,服務(wù)器會(huì)再一次發(fā)送源文件,但如果文件未被改變,發(fā)送源文件太浪費(fèi)帶寬了,只要發(fā)送一個(gè)文件未被更改的短信息標(biāo)示就好了。
緩存+更新終極版
服務(wù)器返回的文件以及額外信息,其中Etag 是 對(duì)請(qǐng)求文件的編碼,如果請(qǐng)求文件在服務(wù)端未被修改,這個(gè)值就不會(huì)變。
Cache-Control: max-age=300;
ETag:W/"e-cbxLFQW5zapn79tQwb/g6Q"
當(dāng)超過(guò)時(shí)間間隔的時(shí)候,重新發(fā)請(qǐng)求獲取源文件的時(shí)候,在發(fā)送請(qǐng)求的時(shí)候附帶剛剛保存的文件的ETag ( If-None-Match:W/"e-cbxLFQW5zapn79tQwb/g6Q"),之后于ETag進(jìn)行比較,如果二者相等,則發(fā)送個(gè)短消息(響應(yīng)頭,不包含圖片內(nèi)容, 304),如果二者不等則發(fā)送新文件和新的 ETag,瀏覽器獲取新文件并更新該文件的 Etag。(瀏覽器的默認(rèn)行為。)
與 ETag 類似功能的是Last-Modified/If-Modified-Since。當(dāng)資源過(guò)期時(shí)(max-age超時(shí)),發(fā)現(xiàn)資源具有Last-Modified聲明,則再次向web服務(wù)器請(qǐng)求時(shí)帶上頭 If-Modified-Since,表示上次服務(wù)器告知的文件修改的時(shí)間。web服務(wù)器收到請(qǐng)求后發(fā)現(xiàn)有頭If-Modified-Since 則與被請(qǐng)求資源的最后修改時(shí)間進(jìn)行比對(duì)。若最后修改時(shí)間較新,說(shuō)明資源又被改動(dòng)過(guò),則響應(yīng)整片資源內(nèi)容(200);若最后修改時(shí)間較舊,說(shuō)明資源無(wú)新修改,則響應(yīng)HTTP 304 ,告知瀏覽器繼續(xù)使用所保存的cache。第一次去請(qǐng)求,響應(yīng)頭中存在Last-modified,刷新后第二次請(qǐng)求,請(qǐng)求頭中有if-modified-since。
.html不會(huì)緩存,.css和圖片都會(huì)
原因:圖片和CSS的請(qǐng)求都是HTML到達(dá)瀏覽器后,瀏覽器解析發(fā)出的,而HTML是直接輸入U(xiǎn)RL解析出來(lái)的。報(bào)文也存在差異:
- .html文件的報(bào)文中瀏覽器自動(dòng)添加'Cache-Control:max-age=0',也表示.html文件不能使用瀏覽器內(nèi)部緩存。
- 當(dāng)瀏覽器檢測(cè)到狀態(tài)碼是304的時(shí)候,就會(huì)在本地的緩存中拿數(shù)據(jù),做一個(gè)展示。調(diào)試的時(shí)候以為是服務(wù)器發(fā)過(guò)來(lái)的,實(shí)際上并不是。
- 勾選開發(fā)者工具控制臺(tái)Disable cache原理:瀏覽器自動(dòng)在請(qǐng)求報(bào)文上添加:pragma:no-cache。
Expires和Pragma字段對(duì)緩存的控制(http1.0版本)
- Expires
//報(bào)文的響應(yīng)頭
res.setHeader('Expires','web, 23 Jan 2019 07:40:51 GMT')
瀏覽器收到響應(yīng)報(bào)文,看到這個(gè)字段就會(huì)進(jìn)行處理,把它和當(dāng)前瀏覽器的時(shí)間進(jìn)行比較,如果滅有超過(guò)他,則使用瀏覽器自己緩存的數(shù)據(jù),如果超過(guò)了,則重新獲取。
更好的代碼寫法
let data = new Date(Date.now() + 1000*5).toGMTString()
res.setHeader('Expires',data)
- Pragma
//響應(yīng)頭
res.setHeader('Pragma','no-cache')
不要這個(gè)緩存。告訴瀏覽器不要用本地的緩存,要向服務(wù)器重新獲取數(shù)據(jù)。
- 同時(shí)存在
如果這兩個(gè)字段同時(shí)存在,則Pragma的優(yōu)先級(jí)是比較高的。
-Cache-Control-對(duì)字段對(duì)緩存的控制
res.setHeader('Cache-Control','max-age=10')
表示數(shù)據(jù)可以使用10秒,在10秒內(nèi)不需要重新獲取數(shù)據(jù)。可以直接使用瀏覽器內(nèi)部緩存的數(shù)據(jù)。
res.setHeader('Cache-Control','no-cache')
不走緩存,是在響應(yīng)報(bào)文上,是服務(wù)器發(fā)給瀏覽器的。下一次瀏覽器發(fā)送請(qǐng)求的時(shí)候,都會(huì)重新向服務(wù)器要數(shù)據(jù)。
res.setHeader('Cache-Control','no-store')
不存。所有的地方都不進(jìn)行緩存。
-Last-Modified字段對(duì)緩存的控制
Cache-Control: no-cache
LastModified:sXXX
可以進(jìn)行緩存,但是下一次請(qǐng)求之前,不可以直接在緩存中拿數(shù)據(jù),要先問(wèn)服務(wù)器是否可以在本地緩存中中拿,如果服務(wù)器返回304狀態(tài)碼,則表示可以在本地緩存中拿數(shù)據(jù),否則,服務(wù)器返回?cái)?shù)據(jù)。
const http = require('http')
const fs = require('fs')
const path = require('path')
http.createServer((req, res) => {
let filePath = path.join(__dirname, req.url)
fs.readFile(filePath, (err, data) => {
if (err) {
res.writeHead(404, 'not found')
res.end('Oh, Not Found')
} else {
let mtime = Date.parse(fs.statSync(filePath).mtime)
//10秒內(nèi),瀏覽器直接從自己本地拿,10秒后找服務(wù)器要。如果沒(méi)修改,告訴瀏覽器沒(méi)修改就行,如果修改了,給瀏覽器最新的
res.setHeader('Cache-Control', 'max-age=10')
if(!req.headers['if-modified-since']) {
res.setHeader('Last-Modified', new Date(mtime).toGMTString())
res.writeHead(200, 'OK')
res.end(data)
}else {
let oldMtime = Date.parse(req.headers['if-modified-since'])
if(mtime > oldMtime) {
res.setHeader('Last-Modified', new Date(mtime).toGMTString())
res.writeHead(200, 'OK')
res.end(data)
}else {
res.writeHead(304)
res.end()
}
}
}
})
}).listen(8080)
console.log('Visit http://localhost:8080' )
-Etag字段對(duì)緩存的控制
Etag就是對(duì)文件進(jìn)行一個(gè)處理,值的獲取是由自己決定的。需要的文件和這個(gè)值進(jìn)行一個(gè)映射,值變了,文件就變了。
res.setHeader('Etag', md5.update(data).digest('base64'))
//data進(jìn)行一個(gè)計(jì)算,得到md5值,當(dāng)文件內(nèi)容發(fā)生改變的時(shí)候,md5值肯定會(huì)變。把md5的值用base64展示,值會(huì)很短。
當(dāng)攜帶Etag字段的報(bào)文到達(dá)瀏覽器之后,當(dāng)瀏覽器下一次再去請(qǐng)求數(shù)據(jù)的時(shí)候,請(qǐng)求頭中就會(huì)有一個(gè)If-None-Match這個(gè)值,會(huì)把這個(gè)值在發(fā)給服務(wù)器。會(huì)先看一下當(dāng)前文件的新的值,進(jìn)行對(duì)比。實(shí)質(zhì)就是檢測(cè)當(dāng)前文件的完整性。
const http = require('http')
const fs = require('fs')
const path = require('path')
const crypto = require('crypto')
http.createServer((req, res) => {
let filePath = path.join(__dirname, req.url)
fs.readFile(filePath, (err, data) => {
if (err) {
res.writeHead(404, 'not found')
res.end('Oh, Not Found')
} else {
// example1
// let md5 = crypto.createHash('md5')
// res.setHeader('Etag', md5.update(data).digest('base64'))
// res.writeHead(200, 'OK')
// res.end(data)
// example2
console.log(req.headers['if-none-match'])
let oldEtag = req.headers['if-none-match']
if(!oldEtag) {
let md5 = crypto.createHash('md5')
res.setHeader('Etag', md5.update(data).digest('base64'))
res.writeHead(200, 'OK')
res.end(data)
} else {
let newEtag = crypto.createHash('md5').update(data).digest('base64')
if(oldEtag !== newEtag) {
res.setHeader('Etag', newEtag)
res.writeHead(200, 'OK')
res.end(data)
}else {
res.writeHead(304)
res.end()
}
}
}
})
}).listen(8080)
console.log('Visit http://localhost:8080' )
小結(jié):
- 在互聯(lián)網(wǎng)上,域名通過(guò)DNS服務(wù)映射到IP地址之后訪問(wèn)目標(biāo)網(wǎng)站,也就是說(shuō),當(dāng)請(qǐng)求到達(dá)服務(wù)器時(shí),已經(jīng)是已IP地址形式訪問(wèn)了。
- 代理:是一種具有轉(zhuǎn)發(fā)功能的應(yīng)用程序,它扮演了位于服務(wù)器和客戶端“中間人”的角色,接收由客戶端發(fā)送的請(qǐng)求并轉(zhuǎn)發(fā)給服務(wù)器,同時(shí)也接收服務(wù)器返回的響應(yīng)并轉(zhuǎn)發(fā)給客戶端。代理不會(huì)改變請(qǐng)求URI。每次通過(guò)代理服務(wù)器轉(zhuǎn)發(fā)請(qǐng)求或者響應(yīng)的時(shí)候,會(huì)追加寫入Via首部信息。
GET/HTTP/1.1 Via:proxy1。- 緩存代理(利用緩存技術(shù));
- 透明代理,不對(duì)報(bào)文做任何加工的代理叫透明代理。
- 網(wǎng)關(guān):是轉(zhuǎn)發(fā)其他服務(wù)器通信數(shù)據(jù)的服務(wù)器,接收從客戶端發(fā)來(lái)的請(qǐng)求時(shí),它就像自己擁有資源的服務(wù)器一樣,對(duì)請(qǐng)求進(jìn)行處理。利用網(wǎng)關(guān)可以由HTTP請(qǐng)求轉(zhuǎn)化為其他協(xié)議通信。
- 隧道:是在相隔甚遠(yuǎn)的客戶端和服務(wù)器兩者之間進(jìn)行中轉(zhuǎn),并保持雙方通信連接的應(yīng)用程序。隧道的目的是確??蛻舳四芘c服務(wù)器進(jìn)行安全的通信,本身 不會(huì)去解析HTTP請(qǐng)求,請(qǐng)求保持原樣中轉(zhuǎn)給之后的服務(wù)器。隧道會(huì)在通信雙方斷開連接時(shí)結(jié)束。
- 需要兼容HTTP1.0的時(shí)候需要使用Expires,不然可以考慮直接使用Cache-Control
- 需要處理一秒內(nèi)多次修改的情況,或者其他Last-Modified處理不了的情況,才使用ETag,否則使用Last-Modified。
- 對(duì)于所有可緩存資源,需要指定一個(gè)Expires或Cache-Control,同時(shí)指定Last-Modified或者Etag。
- 可以通過(guò)標(biāo)識(shí)文件版本名、加長(zhǎng)緩存時(shí)間的方式來(lái)減少304響應(yīng)。