静态服务器(无缓存无更新请求数据)
- 优点:简单。
- 缺点:每次请求必须查找返回原始文件,浪费带宽。
有缓存-无更新请求数据(浏览器本地缓存)
- 优点:节省资源,速度快。
- 缺点:服务器缓存中的数据变了,浏览器不知道数据是否发生改变。
缓存作用:
缓存是指代理服务器或客户端本地磁盘内保存的资源副本,利用缓存可减少对源服务器的访问,可以节省通信流量和通信时间。
有缓存有更新请求数据
- 主要原理:请求被响应的时候,响应报文中有一个Expires :Mon,10 Dec 1990 02:25:22GMT(过期时间),再一次进行请求的时候,用本地的时间与过期时间进行比较,如果本地时间小于过期时间,那么从缓存中获取,如果本地时间大于过期时间,重新向服务器发送请求获取,再一次发送新的过期时间。
- 优点:缓存可控制。
- 缺点:控制的功能太单一,这种格式的时间和容易写错。
有缓存+更新机制升级版
Cache-Control: max-age=300;
以上代码代表时间间隔,如果再一次的请求在时间间隔300s之内,就在缓存中获取,否则从服务器获取。
- Cache-Control还有其他值:
-
Public
表示响应可被任何中间节点缓存,如 Browser <-- proxy1 <-- proxy2 <-- Server,中间的proxy可以缓存资源,比如下次再请求同一资源proxy1直接把自己缓存的东西给 Browser 而不再向proxy2要。 -
Private
表示中间节点不允许缓存,对于Browser <-- proxy1 <-- proxy2 <-- Server,proxy 会老老实实把Server 返回的数据发送给proxy1,自己不缓存任何数据。当下次Browser再次请求时proxy会做好请求转发而不是自作主张给自己缓存的数据。 -
no-cache
表示不使用 Cache-Control的缓存控制方式做前置验证,而是使用 Etag 或者Last-Modified字段来控制缓存。不走缓存,响应报文,是服务器发给浏览器的。浏览器在一次发送请求时,发现这个字段,就不会再缓存中获取数据了,而是再一次向服务器发送请求。 缓存只是本地缓存,而不是服务器对应的缓存。报文会缓存,但是不会使用。 -
no-store
,真正的不缓存任何东西。浏览器会直接向服务器请求原始文件,并且请求中不附带 Etag 参数(服务器认为是新请求)。不存,所有的流程都不进行缓存。连报文都不会进行缓存,啥都不缓存。 -
max-age
,表示当前资源的有效时间,单位为秒。
-
- 优点: 缓存控制功能更强大
- 缺点: 不够完美,超过时间间隔再向服务器要文件的时候,服务器会再一次发送源文件,但如果文件未被改变,发送源文件太浪费带宽了,只要发送一个文件未被更改的短信息标示就好了。
缓存+更新终极版
服务器返回的文件以及额外信息,其中Etag 是 对请求文件的编码,如果请求文件在服务端未被修改,这个值就不会变。
Cache-Control: max-age=300;
ETag:W/"e-cbxLFQW5zapn79tQwb/g6Q"
当超过时间间隔的时候,重新发请求获取源文件的时候,在发送请求的时候附带刚刚保存的文件的ETag ( If-None-Match:W/"e-cbxLFQW5zapn79tQwb/g6Q"),之后于ETag进行比较,如果二者相等,则发送个短消息(响应头,不包含图片内容, 304),如果二者不等则发送新文件和新的 ETag,浏览器获取新文件并更新该文件的 Etag。(浏览器的默认行为。)
与 ETag 类似功能的是Last-Modified/If-Modified-Since。当资源过期时(max-age超时),发现资源具有Last-Modified声明,则再次向web服务器请求时带上头 If-Modified-Since,表示上次服务器告知的文件修改的时间。web服务器收到请求后发现有头If-Modified-Since 则与被请求资源的最后修改时间进行比对。若最后修改时间较新,说明资源又被改动过,则响应整片资源内容(200);若最后修改时间较旧,说明资源无新修改,则响应HTTP 304 ,告知浏览器继续使用所保存的cache。第一次去请求,响应头中存在Last-modified,刷新后第二次请求,请求头中有if-modified-since。
.html不会缓存,.css和图片都会
原因:图片和CSS的请求都是HTML到达浏览器后,浏览器解析发出的,而HTML是直接输入URL解析出来的。报文也存在差异:
- .html文件的报文中浏览器自动添加'Cache-Control:max-age=0',也表示.html文件不能使用浏览器内部缓存。
- 当浏览器检测到状态码是304的时候,就会在本地的缓存中拿数据,做一个展示。调试的时候以为是服务器发过来的,实际上并不是。
- 勾选开发者工具控制台Disable cache原理:浏览器自动在请求报文上添加:pragma:no-cache。
Expires和Pragma字段对缓存的控制(http1.0版本)
- Expires
//报文的响应头
res.setHeader('Expires','web, 23 Jan 2019 07:40:51 GMT')
浏览器收到响应报文,看到这个字段就会进行处理,把它和当前浏览器的时间进行比较,如果灭有超过他,则使用浏览器自己缓存的数据,如果超过了,则重新获取。
更好的代码写法
let data = new Date(Date.now() + 1000*5).toGMTString()
res.setHeader('Expires',data)
- Pragma
//响应头
res.setHeader('Pragma','no-cache')
不要这个缓存。告诉浏览器不要用本地的缓存,要向服务器重新获取数据。
- 同时存在
如果这两个字段同时存在,则Pragma的优先级是比较高的。
-Cache-Control-对字段对缓存的控制
res.setHeader('Cache-Control','max-age=10')
表示数据可以使用10秒,在10秒内不需要重新获取数据。可以直接使用浏览器内部缓存的数据。
res.setHeader('Cache-Control','no-cache')
不走缓存,是在响应报文上,是服务器发给浏览器的。下一次浏览器发送请求的时候,都会重新向服务器要数据。
res.setHeader('Cache-Control','no-store')
不存。所有的地方都不进行缓存。
-Last-Modified字段对缓存的控制
Cache-Control: no-cache
LastModified:sXXX
可以进行缓存,但是下一次请求之前,不可以直接在缓存中拿数据,要先问服务器是否可以在本地缓存中中拿,如果服务器返回304状态码,则表示可以在本地缓存中拿数据,否则,服务器返回数据。
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秒内,浏览器直接从自己本地拿,10秒后找服务器要。如果没修改,告诉浏览器没修改就行,如果修改了,给浏览器最新的
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字段对缓存的控制
Etag就是对文件进行一个处理,值的获取是由自己决定的。需要的文件和这个值进行一个映射,值变了,文件就变了。
res.setHeader('Etag', md5.update(data).digest('base64'))
//data进行一个计算,得到md5值,当文件内容发生改变的时候,md5值肯定会变。把md5的值用base64展示,值会很短。
当携带Etag字段的报文到达浏览器之后,当浏览器下一次再去请求数据的时候,请求头中就会有一个If-None-Match
这个值,会把这个值在发给服务器。会先看一下当前文件的新的值,进行对比。实质就是检测当前文件的完整性。
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' )
小结:
- 在互联网上,域名通过DNS服务映射到IP地址之后访问目标网站,也就是说,当请求到达服务器时,已经是已IP地址形式访问了。
- 代理:是一种具有转发功能的应用程序,它扮演了位于服务器和客户端“中间人”的角色,接收由客户端发送的请求并转发给服务器,同时也接收服务器返回的响应并转发给客户端。代理不会改变请求URI。每次通过代理服务器转发请求或者响应的时候,会追加写入Via首部信息。
GET/HTTP/1.1 Via:proxy1
。- 缓存代理(利用缓存技术);
- 透明代理,不对报文做任何加工的代理叫透明代理。
- 网关:是转发其他服务器通信数据的服务器,接收从客户端发来的请求时,它就像自己拥有资源的服务器一样,对请求进行处理。利用网关可以由HTTP请求转化为其他协议通信。
- 隧道:是在相隔甚远的客户端和服务器两者之间进行中转,并保持双方通信连接的应用程序。隧道的目的是确保客户端能与服务器进行安全的通信,本身 不会去解析HTTP请求,请求保持原样中转给之后的服务器。隧道会在通信双方断开连接时结束。
- 需要兼容HTTP1.0的时候需要使用Expires,不然可以考虑直接使用Cache-Control
- 需要处理一秒内多次修改的情况,或者其他Last-Modified处理不了的情况,才使用ETag,否则使用Last-Modified。
- 对于所有可缓存资源,需要指定一个Expires或Cache-Control,同时指定Last-Modified或者Etag。
- 可以通过标识文件版本名、加长缓存时间的方式来减少304响应。