相比于 HTTP 1.0,HTTP 1.1引入了新的特性:javascript
Host
头,可以使不一样域名配置在同一个IP地址的服务器上。在 HTTP 1.1 中, Connection: Keep-Alive
字段默认开启,这个字段容许服务端和客户端保持一个长链接,当客户端发送另外一个请求时,它会复用同一个链接。这个链接会一直持续到客户端或服务器端认为会话已经结束,其中一方中断链接。中断链接的取决与否取决于 Keep-Alive
字段中的:html
s
,超过设置的时间后断开链接专业的名词解释能够查看中文WIKI:连接java
HTTP管线化(英语:HTTP pipelining)是将多个 HTTP 请求(request)整批提交的技术,而在发送过程当中不需先等待服务器的回应。 流水线操做创建在长链接之上,能够将全部的 HTTP 请求一次性发出,而无需关心上一次发送请求的状态,虽说客户端一次性可以发出全部的请求,可是在服务端接收到的请求仍是一一进行处理的,若是当服务端返回的其中一个响应阻塞后,接下来的响应也会被阻塞。 node
在长链接请求中,没法继续使用以前的方法来判断数据是否彻底发送完毕,可是咱们能够根据 Content-Length
的长度是否为 0 来判断数据是否传输完毕,请求传输的内容与长度必须保持一致,不然内容将会被截断。而且这种方法有一个弊端,那就是当发送的内容足够大时,服务器都须要去计算 content
的长度,致使性能下降和速度变慢,因此咱们须要一个更好的机制来检测长链接的请求开始与结束。 下面一段 node
代码演示了当 Content-Lenth
长度与实际内容长度不一致致使内容被截断的例子web
const http = require('http');
const fs = require('fs');
function handle(req, res) {
res.setHeader('Content-Length', '12');
res.end('1234567890123456');
}
const server = http.createServer(handle);
server.listen(3000);
console.log('Server start at port 3000...');
复制代码
因为 Content-Length
存在的弊端,咱们可使用 Transfer-Encoding
配合 Content-Encoding
来告诉浏览器传输的结果, Transfer-Encoding
的经常使用值为 chunked
,而 Content-Encoding
的做用是告知浏览器采用何种编码(压缩),一般使用的是 gzip
。对资源进行压缩后,再对内容进行分块传输。当最后一个分块长度为0时,则表明数据传输完成。 经过下面使用 Node
建立一个服务器的例子能够了解到什么是分块传输算法
const http = require('http');
function handle(req, res) {
res.setHeader('Content-type', 'text/html; charset=UTF-8');
res.setHeader('Transfer-Encoding', 'chunked');
let i = 0;
const timer = setInterval(()=>{
if(i < 5) {
res.write(`<p>this is ${i} chunk</p>`);
i ++;
}
else {
res.end('<p>this is last chunk</p>');
clearInterval(timer);
}
}, 500);
}
const server = http.createServer(handle);
server.listen(3000);
console.log('Server start at port 3000...');
复制代码
注: HTTP2 再也不支持使用 chunked
分块机制传输数据,有了更好的流传输机制,详细的会在以后的文章说起json
相比 HTTP 1.0,HTTP 1.1 新增了若干项缓存机制:浏览器
详细的缓存机制将会在下面的章节详解缓存
强缓存,是浏览器优先命中的缓存,速度最快。当咱们在状态码后面看到 (from memory disk) 时,就表示浏览器从内存种读取了缓存,当进程结束后,也就是 tab 关闭之后,内存里的数据也将不复存在。只有当强缓存不被命中的时候,才会进行协商缓存的查找。bash
在 HTTP 1.0 时代,使用的是 Pragma: no-cache
字段来进行缓存的判断,因为如今已经步入 1.1 时代,更多的由 Cache-Control
控制,因此建议只在须要兼容 HTTP/1.0 客户端的场合下应用 Pragma 首部。
该字段表示的是,设定的时间为缓存的有效时间,当发生请求时,浏览器将会把 Expires
的值与本地时间进行对比,若是本地时间小于设置的时间,则读取缓存。 Expires
的值为标准的 GMT 格式:
Expires: Wed, 21 Oct 2015 07:28:00 GMT
复制代码
由于对比的是本地时间,因此也存在着弊端:当本地时间与服务端时间不一致时,没法达到预期的资源读取结果。 注 :
Expires
的字段设置为 0 时,表明该资源已通过期Cache-Control
响应头设置了 "max-age" 或者 "s-max-age" 指令,那么 Expires
头会被忽略。因为 Expires
的局限性, Cache-Control
登场了 (具体的参数能够点击查看MDN) ,下面说明几个经常使用的字段
补充说明: 一、关于这no-store 和 no-cache 的区别,能够查看 stackoverflow 的这个提问:连接
Exactly checking
Last-Modified
orETag
. Client would ask server if it has new version of data using those headers and if the answer is no it will serve cached data.
也就是说,若是将 Cache-Control
设置为 no-cache
后,那么服务器将去验证 Last-Modeified
、 ETag
等字段,而 no-store
的做用则是不进行资源的缓存 二、max-age 指的是从当前时间开始计算的秒数,好比客户端初次请求某个资源文件的时间是18:00,假设 max-age
为 600秒,那么就表明着这个资源将在 18:10 过时 下面是一个使用 node 建立具备缓存 js 文件服务器的例子 文件结构以下:
favicon.ico
index.html
test.js
package.json
复制代码
const http = require('http');
const fs = require('fs');
function handle(req, res) {
if(req.url === '/test.js') {
const js = 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('Cache-Control', 'max-age=600');
const html = fs.readFileSync('./index.html');
res.end(html);
}
}
const server = http.createServer(handle);
server.listen(3000);
console.log('Server start at port 3000...');
复制代码
咱们将 max-age
设置为 600秒,那么这个 js 文件将在10分钟以内都使用缓存
当浏览器没有命中强缓存后,便会命中协商缓存,协商缓存由如下几个 HTTP 字段控制
服务端将资源传送给客户端的时候,会将资源最后的修改时间以 Last-Modified: GMT
的形式加在实体首部上返回
Last-Modified: Fri, 22 Jul 2019 01:47:00 GMT
复制代码
客户端接收到后会为此资源信息作上标记,等下次从新请求该资源的时候将会带上时间信息给服务器作检查,若传递的值与服务器上的值一致,则返回 304
,表示文件没有被修改过,若时间不一致,则从新进行资源请求并返回 200
。 那么在从新请求的时候,客户端要用什么方式去传递时间呢?答案是使用 If-Modified-Since
。
在客户端从新请求资源的时候,将会在请求头添加 If-Modifed-Since: GMT
字段传递给服务端,服务端接收后会与当前该文件的最后修改时间对比,若是时间一致则返回 304
,若是不一致则传递最新的资源并返回 200
状态码。
该值告诉服务器,若Last-Modified没有匹配上(资源在服务端的最后更新时间改变了),则应当返回412
(Precondition Failed) 状态码给客户端。 Last-Modified 存在必定问题,若是在服务器上,一个资源被修改了,但其实际内容根本没发生改变,会由于Last-Modified时间匹配不上而返回了整个实体给客户端(即便客户端缓存里有个如出一辙的资源)。 这个字段的一个典型应用场景就是断点续传:当下载的资源内容被改变时,不该该继续返回以前的资源内容。
为了解决上面资源修改可是内容却没被修改,却依旧返回最新的资源的问题,ETag是一种比较好的解决方案, 服务端经过某种算法(好比 MD5)计算出该资源的惟一标致符,在响应资源的时候,将会添加在实体首部字段连同资源一并返回给客户端
ETag: "5b6d63d2-61afa"
复制代码
客户端接收到后将会保存该信息,而且在下一次请求中附上该信息给服务端,服务器只须要比较客户端传来的ETag跟本身服务器上该资源的ETag是否一致,就能很好地判断资源相对客户端而言是否被修改过了。 若是服务端检测到传递过来的值与服务器端的不一致,则返回最新的资源和 200
状态码,不然返回 304
告知客户端使用缓存文件 那么客户端如何告知服务端 ETag 的相关信息呢?能够经过请求首部字段 If-Match
和 If-Node-Match
来进行传递
示例为 If-None-Match: "5d8c72a5edda8d6a:3239"
该字段告知服务端若是 ETag 没匹配上须要重发资源数据,不然直接回送304
和响应报头便可。 当前各浏览器均是使用的该请求首部来向服务器传递保存的 ETag 值。
告诉服务器若是没有匹配到ETag,或者收到了 "*" 值而当前并无该资源实体,则应当返回412
(Precondition Failed) 状态码给客户端。不然服务器直接忽略该字段。 须要注意的是,若是资源是走分布式服务器(好比CDN)存储的状况,须要这些服务器上计算ETag惟一值的算法保持一致,才不会致使明明同一个文件,在服务器A和服务器B上生成的ETag却不同。
注:If-None-Math 的 优先级比 If-Modified-Since 的优先级更高,有了 If-None-Match 字段后 If-Modified-Since 字段将会被忽略
字段名称 | 说明 |
---|---|
Cache-Control | 控制缓存的行为 |
Pragma | HTTP 1.0 遗留字段,使用 "no-cache" 做为是否缓存依据 |
字段名称 | 说明 |
---|---|
If-Match | 比较 ETag 是否一致 |
If-None-Match | 比较 ETag 是否不一致 |
If-Modified-Since | 比较资源最后的更新时间是否一致 |
If-Unmodified-Since | 比较资源最后的更新时间是否不一致 |
字段名称 | 说明 |
---|---|
ETag | 资源的匹配信息(经过强比较算法生成值) |
字段名称 | 说明 |
---|---|
Expires | HTTP 1.0 遗留字段,资源的过时时间(取决于客户端本地时间) |
Last-Modified | 资源的最后一次修改时间 |
强缓存 --> 协商缓存 Cache-Control
-> Expires
-> ETag
-> Last-Modified