在最近的前端面试中,http成为了热门的考察内容。写这篇文章主要是为了对http1.1的新特性有一个完整的知识体系,同时也是为了能够深入了解常见的缓存机制。与HTTP1.0相比,HTTP1.1引入了一个新特性:连接可以重复使用,节省了多次打开TCP连接加载网页文档资源的时间。增加流水线,允许在第一个回复完全发送之前发送第二个请求,从而减少通信延迟。支持响应分块。引入额外的缓存控制机制。引入内容协商机制,包括语言、编码、类型等,让客户端和服务器商定交换最合适的内容。Host标头允许在具有相同IP地址的服务器上配置不同的域名。连接重用在HTTP1.1中,Connection:Keep-Alive字段默认启用。该字段允许服务器和客户端保持长连接。当客户端发送另一个请求时,它将重用同一个连接。此连接将继续,直到客户端或服务器认为会话结束,并且其中之一断开连接。连接是否中断取决于Keep-Alive字段:timeout:确定当前tcp的最长连接时间,单位s,超过设置时间后断开max:确定最可重用的当前连接可以参考中文WIKI流水线(httppipelining)专业术语:链接HTTP流水线(英文:HTTPpipelining)是一种在发送过程中不等待服务器响应,批量提交多个HTTP请求(requests)的技术。管道操作建立在长连接之上,可以一次发送所有的HTTP请求,而不用关心上次发送请求的状态。虽然客户端可以一次发送所有的请求,但是在服务器端收到的请求仍然是一个一个处理的。如果服务器返回的响应之一被阻塞,则后续响应也将被阻塞。responseblock的content-length字段在长连接请求中,不能用前面的方法判断数据是否发送完毕,但是我们可以根据其长度判断数据是否发送完毕Content-Length为0,请求传输的内容必须与长度一致,否则内容会被截断。而且这种方式有一个缺点,就是当发送的内容足够大时,服务端需要计算内容的长度,导致性能下降,速度变慢,所以我们需要更好的机制来检测长连接的启动请求结束。下面这段节点代码演示了当Content-Lenth长度与实际内容长度不一致时截断内容的例子consthttp=require('http');constfs=require('fs');functionhandle(req,res){res.setHeader('Content-Length','12');res.end('1234567890123456');}constserver=http.createServer(handle);server.listen(3000);console.log('服务器启动于端口3000...');因为Content-Length的长度超过12,内容被截断了Transfer-Encoding和Content-Encoding由于Content-Length的缺点,我们可以使用Transfer-Encoding和Content-Encoding来告诉浏览器传输结果,Transfer-Encoding的常用值是chunked,而Content-Encoding的作用是告诉浏览器使用什么编码(压缩),通常使用gzip。资源压缩后,内容以块的形式传输。当最后一个块的长度为0时,表示数据传输完成。通过下面这个使用Node创建服务器的例子,可以理解什么是分块传输consthttp=require('http');functionhandle(req,res){res.setHeader('Content-type','text/html;字符集=UTF-8');res.setHeader('传输编码','分块');让我=0;consttimer=setInterval(()=>{if(i<5){res.write(`
这是${i}块
`);i++;}else{res.end('这是最后一块
');clearInterval(timer);}},500);}constserver=http.createServer(handle);server.listen(3000);console.log('Server从端口3000开始...');注意:HTTP2不再支持使用chunkedblock机制传输数据,有更好的streaming机制,后面的文章会详细讲到。与HTTP1.0相比,HTTP1.1增加了几种新的缓存机制:If-Modefined-SinceIf-Unmodified-SinceIf-MatchIf-None-MatchET-tag具体的缓存机制将在后面的章节中详细讲解HTTP缓存机制StrongcachingStrongcaching是浏览器最先命中的缓存,速度是最快的。当我们在状态码后面看到(frommemorydisk)时,说明浏览器已经从内存中读取了缓存。当进程结束,即tab关闭后,内存中的数据将不复存在。只有没有命中强缓存才会执行协商缓存查找。在HTTP1.0时代,Pargma使用Pragma:no-cache字段来判断缓存。由于进入了1.1时代,更多的是受Cache-Control的控制,所以只在需要兼容HTTP/1.0客户端的时候才推荐使用。接下来应用Pragma标头。Expires该字段表示设置的时间为缓存的有效时间。当有请求发生时,浏览器会将Expires的值与本地时间进行比较。如果本地时间小于设置的时间,就会读取缓存。Expires的值是标准的GMT格式:Expires:Wed,21Oct201507:28:00GMT与本地时间比较,所以也有缺点:当本地时间与服务器时间不一致时,预期的无法获取资源读取结果。注意:当Expires字段设置为0时,表示该资源已过期。如果在Cache-Control响应标头中设置了“max-age”或“s-max-age”指令,Expires标头将被忽略。Cache-Control由于Expires的限制,Cache-Control登场了(具体参数请点击查看MDN)。下面是一些常用的字段no-store:缓存不应该存储任何关于客户端请求或服务器响应的信息。no-cache:强制缓存在发布缓存副本之前将请求提交给源服务器进行验证。max-age:相对过期时间,单位秒(s),告诉服务器有多少资源是有效的,不需要请求服务器额外解释:1.no-store和no-cache的区别,可以查stackoverflow这个问题:链接ExactlycheckingLast-ModifiedorETag。客户端会询问服务器是否有使用这些标头的新版本数据,如果答案是否定的,它将提供缓存数据。即如果Cache-Control设置为noAfter-cache,服务器会对Last-Modeified、ETag等字段进行校验,no-store功能是不缓存资源。2.max-age是指距离当前时间的秒数,比如客户端第一次请求资源文件的时间是18:00,假设max-age为600秒,则表示资源将在18:10到期。下面是一个使用node创建带缓存的js文件服务器的例子。文件结构如下:favicon.icoindex.htmltest.jspackage.jsonconsthttp=require('http');constfs=require('fs');functionhandle(req,res){if(req.url==='/test.js'){constjs=fs.readFileSync('./test.js');res.writeHead(200,{'Content-Type':'text/javascript','Cache-Control':'max-age=600'});res.end(js);}else{res.setHeader('Content-type','text/html;charset=UTF-8');res.setHeader('Transfer-Encoding','chunked');res.setHeader('缓存控制','max-age=600');consthtml=fs.readFileSync('./index.html');res.end(html);}}constserver=http.createServer(handle);server.listen(3000);console.log('服务器启动于端口3000...');我们设置max-age为600秒,那么这个js文件会在10分钟内使用cache协商缓存,当浏览器没有命中强缓存后,再命中协商缓存。协商缓存由以下HTTP字段控制。Last-Modified服务端向客户端发送资源时,会在实体头上以Last-Modified:GMTReturnLast-Modified的形式添加资源的最后修改时间:Fri,22Jul201901:47:00GMT客户端收到这个资源信息后会做标记,下次重新请求资源时会带上时间信息给服务器端校验,传过来的值和服务器端的值一致就返回304,说明文件没有被修改。如果时间不一致,就会重新请求资源,并返回200。那么客户端在重新请求的时候应该怎么打发时间呢?答案是使用If-Modified-Since。If-Modified-Since客户端重新请求资源时,会在请求头中添加If-Modified-Since:GMT字段,传递给服务端。服务器收到后,会与当前文件的最后修改时间进行比较。如果时间一致,则返回304,如果不一致,则传递最新的资源,并返回200状态码。If-Unmodified-Since这个值告诉服务器,如果Last-Modified不匹配(服务器上资源的最后更新时间已经改变),它应该返回一个412(PreconditionFailed)状态码给客户端。Last-Modified有一定的问题。如果一个资源在服务器上被修改了,但它的实际内容根本没有改变,那么整个实体将返回给客户端,因为Last-Modified时间不匹配(即使客户端缓存有完全相同的资源)。该领域的一个典型应用场景是续传:当下载的资源内容发生变化时,不应继续返回之前的资源内容。ETag是解决上述资源修改但内容没有修改,但返回的仍然是最新资源的较好解决方案。ETag是一个更好的解决方案。服务端通过一定的算法(比如MD5)计算出资源的唯一标识,在响应一个资源时,会和资源一起添加到实体头字段中,返回给客户端ETag:"5b6d63d2-61afa"客户端收到这条信息后会保存下来,并在下一次请求时附上这条信息在服务器端,服务器只需要比较客户端发送的ETag和自己服务器上资源的ETag是否一致,以及然后判断该资源相对于客户端是否发生了修改。如果服务端检测到传递的值与服务端不一致,则返回最新的资源和一个200状态码,否则返回304告诉客户端使用缓存文件。那么客户端如何将ETag信息告知服务器呢?可以通过请求头字段If-Match和If-Node-MatchIf-None-Match例子是If-None-Match:"5d8c72a5edda8d6a:3239"该字段通知服务器如果资源数据需要重新发送ETag不匹配,否则直接返回304和响应头。目前,所有浏览器都使用这个请求头来将保存的ETag值传递给服务器。If-Math告诉服务器,如果它与ETag不匹配,或者如果它收到一个“*”值但当前没有资源实体,它应该向客户端返回一个412(PreconditionFailed)状态码。否则,服务器将忽略此字段。需要注意的是,如果资源存储在分布式服务器(如CDN)上,这些服务器上计算ETag唯一值的算法需要保持一致,以免导致服务器上生成相同的文件A和服务器B的ETag不同。注意:If-None-Math的优先级高于If-Modified-Since。对于If-None-Match字段,If-Modified-Since字段将被忽略。相关字段汇总通用头字段名描述Cache-Control控制缓存PragmaHTTP1.0legacy字段的行为,使用“no-cache”作为是否根据请求头字段名描述进行缓存If-Match比较ETag是否是否一致If-None-Match比较ETag是否不一致If-Modified-Since比较资源的最后更新时间是否相同If-Unmodified-Since比较资源的最后更新时间是否不一致。响应头字段的字段名描述了ETag资源的匹配信息(该值是通过强比较算法生成的)。实体头字段描述的字段名ExpiresHTTP1.0legacyfield,Resourceexpirationtime(dependingonclientlocaltime)Last-Modified资源最后修改时间PriorityStrongcaching-->negotiationcacheCache-Control->Expires->ETag->Last-Modified状态码变化信息参考MDN中文WIKIHTTP缓存控制总结HTTP协议中的Transfer-Encoding