isFresh(reqHeaders, resHeaders) { const noneMatch = reqHeaders['if-none-match']; const lastModified = reqHeaders['if-modified-since']; if (!(noneMatch || lastModified)) return false; if(noneMatch && (noneMatch !== resHeaders['etag'])) return false; if(lastModified && lastModified !== resHeaders['last-modified']) return false; return true; }
需要注意的是,http首部字段名是不区分大小写的(但http method应该大写),所以平常在浏览器中会看到大写或小写的首部字段。
但是node的http模块将首部字段都转成了小写,这样在代码中使用起来更方便些。所以访问header要用小写,如reqHeaders['if-none-match']。不过,仍然可以用req.rawreq.rawHeaders来访问原headers,它是一个[name1, value1, name2, value2, ...]形式的数组。
现在来测试一下,因为设置的缓存有效时间是极小的5s,所以强缓存几乎不会命中,所以第二次访问文件会发出新的请求,因为服务端文件并没做什么改变,所以会返回304。
现在来修改一下请求的这张图片,比如修改一下size,目的是让服务端的再验证失败,因而必须给客户端发送200和最新的文件。
接下来把缓存有效时间改大一些,比如10分钟,那么在10分钟之内的重复请求,都会命中强缓存,浏览器不会向服务端发起新的请求(但network依然能观察到这条请求)。
内容编码
服务器在发送很大的文档之前,对其进行压缩,可以节省传输用时。其过程是:
浏览器在访问网站时,默认会携带Accept-Encoding头
服务器在收到请求后,如果发现存在Accept-Encoding请求头,并且支持该文件类型的压缩,压缩响应的实体主体(并不压缩头部),并附上Content-Encoding首部
浏览器收到响应,如果发现有Content-Encoding首部,按其值指定的格式解压报文
对于图片这类已经经过高度压缩的文件,无需再额外压缩。因此,我们需要配置一个字段,指明需要针对哪些类型的文件进行压缩。
default.json
{ ... "zipMatch": "^\\.(css|js|html)$" } static-server.js constructor() { ... this.zipMatch = new RegExp(config.zipMatch); }
用zlib模块来实现流压缩:
compressHandler(readStream, req, res) { const acceptEncoding = req.headers['accept-encoding']; if (!acceptEncoding || !acceptEncoding.match(/\b(gzip|deflate)\b/)) { return readStream; } else if (acceptEncoding.match(/\bgzip\b/)) { res.setHeader('Content-Encoding', 'gzip'); return readStream.pipe(zlib.createGzip()); } else if (acceptEncoding.match(/\bdeflate\b/)) { res.setHeader('Content-Encoding', 'deflate'); return readStream.pipe(zlib.createDeflate()); } }
因为配置了图片不需压缩,在浏览器中测试会发现图片请求的响应中没有Content-Encoding头。
范围请求
最后一步,使服务器支持范围请求,允许客户端只请求文档的一部分。其流程是:
客户端向服务端发起请求
服务端响应,附上Accept-Ranges头(值表示表示范围的单位,通常是“bytes”),告诉客户端其接受范围请求
客户端发送新的请求,附上Ranges头,告诉服务端请求的是一个范围
服务端收到范围请求,分情况响应:
范围有效,服务端返回206 Partial Content,发送指定范围内内容,并在Content-Range头中指定该范围
范围无效,服务端返回416 Requested Range Not Satisfiable,并在Content-Range中指明可接受范围
请求中的Ranges头格式为(这里不考虑多范围请求了):
Ranges: bytes=[start]-[end]
其中 start 和 end 并不是必须同时具有:
如果 end 省略,服务器应返回从 start 位置开始之后的所有字节
如果 start 省略,end 值指的就是服务器该返回最后多少个字节
如果均未省略,则服务器返回 start 和 end 之间的字节
响应中的Content-Range头有两种格式:
当范围有效返回 206 时:
Content-Range: bytes (start)-(end)/(total)
当范围无效返回 416 时:
Content-Range: bytes */(total)
添加函数处理范围请求: