2万字干货 | 金九银十,为你面试HTTP协议部分添砖加瓦

1,306 阅读50分钟

前言

身为 WEB 开发人员,每天与浏览器打交道,对于 HTTP 理应理解透彻,然而发现现在却对 HTTP 的认识知之甚少,有些 WEB 开发人员甚至对状态码了解的都不透彻,只知道 200、404 和 500,方法只知道 get 和 post。所以想对 HTTP 更加深入的学习和了解。这就是我想写这篇文章的初衷。

可能文章相对基础,希望能给大家一点帮助,也是让自己对 HTTP 的理解更深入一些。

1. URI、URL 和 URN

URI(统一资源标识符)是包括 URL 和 URN 统一的定义,用来表示互联网上唯一资源的方式。因为 URN 现在还没有成熟的方案,所以在现实中所讲的 URI 和 URL 其实是一样的。

URL(统一资源定位符)用来描述了一台特定服务器上某资源的特定位置。

完整的 URL 路径包括 9 个部分:

<scheme>://<user>:<password>@<host>:<port>/<path>;<params>?<query>#<frag>

我们依次讲解这 9 部分分别代表的含义:

  1. scheme:表示协议名,常用的协议名有 http、https、file 等,其没有默认值,且其后面必须加上://
  2. user:用户名,当访问某些资源的时候要求加上用户名,但是他是直接在路径上显示的,所以安全性低,不建议使用。当不定义这个参数时,默认值是 anonymous(匿名用户)
  3. password:密码,即访问某些资源时所需要的密码,通常是与 user 配合使用,并和 user 之间用:相隔。它和 user 属性一样,安全性低,不建议使用。默认值主要看浏览器,IE 浏览器其默认值是 IEUser,Netscape Navigator 则是 mozilla
  4. host:主机名,表示因特网上能够访问资源的宿主服务器。可以是主机名例如 www.baidu.com,也可以是 IP 地址。
  5. port:端口号,表示宿主服务器正在监听的端口号,默认端口号是 80。
  6. path:服务器上资源的本地位置,跟之前的 URL 属性用 / 隔开,说白了就是表示该资源位于服务器的哪个地方。
  7. params:某些协议会通过这个属性来指定输入的参数,参数以键值对的形式填写,并且可以输入多个参数,并用 ; 隔开。
  8. query:查询字符串,通常用来缩小请求资源类型的查询范围,写法与 params 一样,都是以键值对的形式显示,并且可以写多个查询参数,他们之前用 & 符号隔开。
  9. frag:表示 URI 所定位的资源内的一个锚点,浏览器可以根据这个锚点跳转到对应的位置。比如对于一个大型文本来说,资源的 URL 会指向整个文本文档,但是我们可以通过 frag 参数,指向某个章节。它如 URL 的其他参数要用 # 隔开。

URN(永久统一资源定位符)当资源移动之后还能被找到,但是现在还没有成熟的使用方案,所以就不过多的介绍了。

2. HTTP 报文

其实最开始听到这个词的时候,我一度认为报文为豹纹 😂,还闹出了笑话。

HTTP 报文是一行一行简单的字符串组成的,从客户端到服务端的 HTTP 报文称之为请求报文,从服务端到客户端的 HTTP 报文称之为响应报文,除此之外没有其他报文。 请求报文和响应报文的格式很类似,只有起始行的格式是不同的,除此之外大致分为三个部分:

  1. 起始行:所有的 HTTP 报文都以一个起始行作为报文的开始。请求报文的起始行说明要做什么,响应报文的起始行说明发生了什么。
    • 请求报文起始行的格式:方法+请求的 URL+版本
    • 响应报文起始行的格式:版本+状态码+原因短语
  2. 首部字段:在报文中可以有零个首部也可以有多个首部,每个首部都包含一个名字,后面跟着冒号:,然后是一个可选的空格,接着是一个值,最后是一个 CRLF(空格),首部是由一个 CRLF 结束的,表示首部和主体分开。
  3. 主体:实体的主体部分包含一个由任意数据组成的数据块。并不是所有报文都包含实体的主体部分,有时候就是一个空行。

3. HTTP 方法

HTTP 方法其实就是用来定义对于资源的操作,其实值得注意的是,并是不每台服务器都支持所有的方法,这与每台服务器的配置有关。

通常我们用到的方法有以下几种:

  1. GET:通常是请求服务器发送某个资源,其在请求时是不需要实体的。
  2. HEAD:它与 GET 类似,但是其返回的响应报文中是不包含实体中主体的,只有首部部分,其主要目的是为了客户端在请求资源的时候,对资源的一种检查,检查该资源是否存在,或者测试该资源是否被修改。
  3. POST:向服务器发送需要被处理的数据,主要用于表单的提交,在请求时需要携带实体。
  4. PUT:将请求的主体部分存储在服务器上,请求时需要携带实体。
  5. TRACE:对可能经过代理服务器传送到服务器上的报文进行追踪,并在目的服务器上发起一个诊断,最终服务器将返回一个 TRACE 响应,并在响应实体中携带它接收到的最原始的报文,这样客户端就可以进行比对,来看数据是否发生修改。
  6. OPTIONS:询问可以在服务器上执行哪些方法,请求时不需要携带实体。
  7. DELETE:请求服务器删除某个资源,但是客户端程序无法保证删除操作一定会被执行,因为 HTTP 规范允许服务器在不通知客户端的情况下,对其进行撤销请求。
  8. CONNECT: 建立连接隧道,用于代理服务器

HTTP 的方法是否可以自定义?

这也是一道面试题,当初我的回答是不可以,现在看来我是错误的。HTTP 支持扩展方法。

扩展方法通常指的是 HTTP/1.1 中没有被定义的方法,即除了上述讲的 8 种以外,但是要注意的是,扩展方法不能随意扩展,其要能够被 HTTP 应用程序所理解。

4. HTTP 状态码

定义服务器对请求的处理结果。

HTTP 状态码由三位数组成,被分为五大类:

  1. 1xx:信息性状态码
  2. 2xx:成功状态码
  3. 3xx:重定向状态码
  4. 4xx:客户端错误状态码
  5. 5xx:服务器错误状态码

接下来我们分析一些具体典型的状态码:

1xx

101 Switching Protocols。在 HTTP 升级为 WebSocket 的时候,如果服务器同意变更,就会发送状态码 101。

2xx

200 OK 请求没问题,实体中的主体部分包含了所请求的资源。

201 Created 用于创建服务器对象的请求。响应的实体主体部分中应该包含各种引用了已创建的资源的 URL。

202 Accepted 请求已被接受,但服务器还没对其执行任何操作,

203 NoN-Authoritative Information 实体首部包含的信息不是来自于源端服务器,而是来自资源的一份副本。

204 No Content 与 200 类似,但是没有实体的主体部分。主要用于在浏览器不转为现实新文档的情况下,对其进行更新(比如刷新一个表单页面)。

205 Reset Content 负责告诉浏览器清除当前页面中的所有 HTML 表单元素。

206 Partial Content 成功执行了一部分或 Range 请求。主要用于分块下载和断点续传。并且响应中必须包含 Content-Page、Date 和 ETag 以及 Content-Location 首部。

3xx

300 Multiple Choices 客户端请求一个实际指向多个资源的 URL 时会返回这个状态码,比如服务器上有某个 HTML 文档的英文和法文版本。返回这个代码时会返回一个选择列表,这时候客户端就可以选择自己需要哪个版本了。

301 Moved Permanently 在请求的 URL 被移除时使用,响应中应该包含现在资源所处的 URL,即永久重定向。

302 Found 与 301 类似,但是这个是临时重定向。

303 See Other 告知客户端应该用另一个 URL 来获取资源,新的 URL 位于响应报文的首部,其首要目的是允许 POST 请求的响应将客户端重定向到某个资源上。

304 Not Modified 如果客户端发出一个 GET 请求的时候服务器会从缓存中调用你要访问的内容,这个时候服务器就可以判断这个页面是不是更新过了,如果未更新过那么会返回这个状态码,即当协商缓存命中时会返回这个状态码。

305 Use Proxy 用来说明必须通过代理来访问资源。

307 Temporary Redirect 与 301 类似,但是响应首部给出的是现在资源的临时 URL。

4xx

400 Bad Request 用于告知客户端它发送了一个错误的请求。

401 Unauthorized 该客户端没有访问该资源的权限。

403 Forbidden 请求被服务器拒绝了,如果服务器想说明是因为说明原因拒绝,那么会在实体的主体中写上说明描述,但是一般情况下该状态码是服务器不想说明原因。

404 Not Found 服务器上没有找到所请求的 URL。

405 Method Not Allowed 发起的请求方法不被服务器允许,应该在响应报文中的 Allow 中,告知客户端支持哪些方法。

406 Not Acceptable 客户端可以指定参数来说明它们愿意接收说明类型的实体,当服务器没有与客户端相匹配的类型实体时,返回该状态码。即资源无法满足客户端的条件。

407 Proxy Authentication Required 与 401 类似,但用于要求对资源进行认证的代理服务器。

408 Request Timeout 服务器等待太长时间。

409 Conflict 多个请求在资源上发生了冲突。

410 Gone 与 404 类似,只是服务器曾经拥有过该资源。

411 Length Required 服务器要求请求时在报文首部填写 Content-Length。

412 Precondition Failed 客户端发起了条件请求,且其中一个条件失败的时候使用。

413 Request Entity Too Large 客户端请求的实体过大。

414 Request-URI Too Long 客户端请求的 URL 过长时。

415 Unsupported Media Type 服务器无法支持客户端所发送的实体的内容类型时。

416 Request Range Not Satisfiable 当请求某个范围内的资源时,但此范围无效或者不满足。

417 Expecation Failed 请求的 Expect 请求首部包含一个期望,但服务器不满足这个期望。

429 Too Many Request: 客户端发送的请求过多。

431 Request Header Fields Too Large 请求头的字段内容太大。

5xx

500 Internal Server Error 服务器出错。

501 Not Implemented 客户端发起的请求超出了服务器的能力范围。

502 Bad Gateway 服务器自身是正常的,但访问的时候出错了。

503 Service Unavailable 服务器忙碌中,暂时无法响应服务。

504 Gateway Timeout 与 408 类似,只是这里的响应来自一个网关或代理,它们在等待另一个服务器对其请求进行响应时超时了。

505 HTTP Version Not Suppored 服务器收到了它无法支持的协议。

5. 跨域

为什么会产生跨域?

浏览器内部有个安全机制,即同源策略。同源策略(Sameoriginpolicy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说 Web 是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。同源策略会阻止一个域的 javascript 脚本和另外一个域的内容进行交互。所谓同源(即指在同一个域)就是两个页面具有相同的协议(protocol),主机(host)和端口号(port)

什么是跨域?

其实上述讲的很清晰,当一个请求 url 的协议、域名、端口三者之间任意一个与当前页面 url 不同即为跨域。跨域的响应将被浏览器所拦截,这里值得注意的是,这是浏览器的行为,并且已经成功在服务器请求数据了,只是浏览器将响应拦截了。

跨域的时有以下几种限制:

  1. 不能读取和修改对方的 DOM
  2. 不能访问对方的 Cookie、IndexDB 和 LocalStorage
  3. 无法进行 XMLHttpRequest 请求

如何解决跨域?

通常解决的跨域的办法指的就是无法进行 XMLHttpRequest 请求,毕竟前后端之间的通信还是数据最重要,那解决的办法有以下几种

JSONP

由于 XMLHttpRequest 对象受到同源策略的限制,但是 script 标签并没有,所以可以通过其的 src 属性,填写目标地址,发送 GET 请求。这就是 JSONP 的核心原理。

使用的时候也很简单,通过创建 script 标签,src 属性填写 URL,再通过回调函数执行请求后的操作。

let jsonp = ({ url, params, callback }) => {
  // 将url和params进行拼接
  const getURL = () => {
    let dataStr = "";
    for (let key in params) {
      dataStr += `${key}=${params[key]}&`;
    }
    dataStr += `callback=${callback}`;
    return `${url}?${dataStr}`;
  };
  callback = callback || Math.random().toString.replace(",", "");
  // 创建 script 元素并加入到当前文档中
  let scriptEle = document.createElement("script");
  scriptEle.src = getURL();
  document.body.appendChild(scriptEle);
  return new Promise((resolve) => {
    window[callback] = (data) => {
      resolve(data);
      document.body.removeChild(scriptEle);
    };
  });
};

JSONP 的方式应该算是最早解决跨域的方法之一,它的局限性很大,只能应用 GET 请求,但是越早的东西就有个共性,就是兼容性好。🤣🤣🤣

CORS

CORS 是一个 W3C 标准,全称是"跨域资源共享"(Cross-origin resource sharing)。CORS 需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE 浏览器不能低于 IE10。它允许浏览器向跨源服务器,发出 XMLHttpRequest 请求,从而克服了 AJAX 只能同源使用的限制。

在 MDN 中所讲,HTTP 访问控制(CORS),CORS 分为两种请求,一种是简单请求,一种是预检请求(非简单请求)

简单请求

简单请求就是客户端在请求报文中通过 Origin 标记自己来源于哪个源,服务器拿到请求之后,在回应时对应地添加 Access-Control-Allow-Origin 字段,如果 Origin 不在这个字段的范围中,那么浏览器就会将响应拦截。如果服务端返回的 Access-Control-Allow-Origin: * 表明,该资源可以被任意外域访问。

预检请求(非简单请求)

预检请求要求必须首先使用 OPTIONS 方法发起一个预检请求到服务器,以获知服务器是否允许该实际请求。"预检请求“的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响。 这里我们运用 MDN 里举的例子来看一下:

var invocation = new XMLHttpRequest();
var url = "http://bar.other/resources/post-here/";
var body = '<?xml version="1.0"?><person><name>Arun</name></person>';

function callOtherDomain() {
  if (invocation) {
    invocation.open("POST", url, true);
    invocation.setRequestHeader("X-PINGOTHER", "pingpong");
    invocation.setRequestHeader("Content-Type", "application/xml");
    invocation.onreadystatechange = handler;
    invocation.send(body);
  }
}

可以看到其预请求的过程:

OPTIONS /resources/post-here/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Origin: http://foo.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type


HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain

送上述可以看出,首先预检请求,运用 OPTIONS 方法,请求头中 Access-Control-Request-Method 和 Access-Control-Request-Headers 字段表示本次的将要的请求方法和将要子携带的自定义头部字段,服务端接收到后,根据此决定来判断是否允许此次请求。

如果匹配则返回预检请求的响应:

Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400

预检请求成功后将发布实际的请求。

Proxy

现在本地开发通常使用 Porxy 来进行跨域处理,记住是本地开发。webpack 中的 proxy 只是一层代理,用于把指定的 path,代理去后端提供的地址,背后使用 node 来做 server。原理的话其实是用 webpack 中的 http-proxy-middleware 这个中间件,这个 http 代理中间件,实现请求转发给其他服务器。

Nginx

Nginx 通常叫做反向代理,这里就不过多阐述了。

6. HTTP 代理

最简单的 HTTP 只有客户端和服务器,但是有些时候,我们并不希望客户端直接去访问服务器,这个时候就可以使用代理作为中间层,客户端访问代理,然后代理访问服务器,这样可以减少直接访问服务器带来的安全性问题,也可以对客户端的数据进行过滤。举个例子,学校可以通过代理让学生访问不到"你懂得"的网站。

代理分为两个类型,一个是客户端代理(正向代理),一个是服务端代理(反向代理),反向代理实现有很多,下面就不一一展述了。

客户端代理的实现

  1. 手动配置:

通过浏览器的设置进行配置,每个浏览器不一样,谷歌浏览器可以安装一个 Proxy SwitchOmega 的插件。

  1. PAC 文件

提供一个 URI 指向一个用 Javascript 语言编写的代理自动配置文件,客户端会取回 Javascript 文件,并运行它以决定是否应该使用这个代理,如果时的话,应该使用哪个代理服务器。

  1. WPAD

有些浏览器支持 WEB 代理自动发现协议(WPAD),这个协议会自动检测出浏览器可以从哪个"配置服务器"下载到一个自动配置文件。

相关字段

通常来说代理起到了欺上瞒下的作用,那如果想要追踪经过代理的报文流,以检测出各种问题的话,via 字段就凸显出重要性了。

via 是一个通用字段,请求头和响应头都会出现,当报文经过代理服务器的时候,就会在 via 后面添加代理服务器的信息,中间用逗号隔开。

例如当客户端和服务器中间经过两个代理服务器:

客户端-->代理1-->代理2-->服务器

则服务器收到的请求报文中 Via 字段应为: Via: proxy_service1, proxy_service2

客户端收到响应报文中 Via 字段的顺序则正好相反:

Via: proxy_service2, proxy_service1

Via 虽好,但是有个问题,它只解决了客户端和源服务器判断是否存在代理的问题,还不能知道对方的真实信息。

所以接下来要在介绍两个字段

X-Forwarded-For

X-Forwarded-For 的意思是"为谁而转发",它的作用和 via 是一样的,同样是将经过的代理服务器信息添加在字段后面,但是与 via 不同的是,它添加的是请求方的 IP,所以字段的最左边就是客户端的 IP 地址。

貌似是一个最佳的解决方案,但是多少会有问题存在:

  1. X-Forwarded-For 操作代理信息必须要解析 HTTP 报文头,这无疑是增加了工作量,性能肯定会下降
  2. X-Forwarded-For 需要修改原始报文,这在有些情况下是不允许的。

X-Real-IP

X-Real-IP 从字段名称就可以看出来,就是获取真实的 IP 地址,它不会追加客户端和服务器中间代理的信息。

代理协议

在 X-Forwarded-For 字段中我们有说道,它存在一些的问题,为了解决这些问题,代理协议就被提出来了,它由代理软件 HAProxy 所定义。

一般使用明文版本,只需要在 HTTP 请求行上面加上这样格式的文本即可:

// PROXY + TCP4/TCP6 + 请求方地址 + 接收方地址 + 请求端口 + 接收端口
PROXY TCP4 0.0.0.1 0.0.0.2 1111 2222
GET / HTTP/1.1
...

7.缓存

缓存是面试中经常要问到的内容,因为跟项目优化有着密切的关系。

缓存分为两种,一种是强缓存,一种是协商缓存。

强缓存

我们说的缓存主要是 Cache-Control 字段,该字段有三个属性:

  1. public:是指 HTTP 在请求返回的过程中,经过的所有节点包括代理服务器和客户端,都可以对此资源进行缓存。
  2. private:只有发起请求的浏览器才可以进行缓存
  3. no-cache:每次缓存时都要到服务器端验证,是否可以缓存,进入协商缓存阶段。

到期

即缓存什么时候到期。

max-age = 设置到期时间,即多少秒后缓存到期

s-maxage = 只有在代理服务器的时候才会生效,其在代理服务器中会代替 max-age

max-stale = 在 max-age 过期之后,如果返回的资源中有这个设置,那就代表如果 max-age 过期了,只要这个时间还没过期,那就可以继续使用该过期的资源,而不用去服务端请求资源

重新验证

must-revalidate:当缓存过期后,必须向源服务端重新获取数据,再来验证这部分数据是否真的过期了。

proxy-revalidate:代理服务器当缓存后期后,必须去源服务器重新请求数据进行验证

其他

no-store:任何节点都不能缓存

no-transform:告诉代理服务器,不要随便改动资源

协商缓存

Last-Modified:上次修改时间。通常和 If-Modified-Since:或者 If-Unmodified-Since:配合使用。当浏览器第一次对该资源请求的时候,服务器返回的响应头中会标注 Last-Modified,即改文件最后修改的时间,当浏览器再次请求该资源时,请求头中会携带 If-Modified-Since 字段,传输到服务器时,服务器会判断该字段所携带的时间与当前资源修改时时间是否相同,如果不相同则会返回新的资源,跟普通请求资源的流程一样,当相同时,则会返回 304 状态码,告诉浏览器直接去找缓存。

Etag:数据签名。一个资源对它的内容会产生唯一的一个签名,只要数据修改该签名就会发生变化。配合 If-Match:或者 If-Non-Match:<>一块使用,该过程与 Last-Modified 和 If-Modified-Since 过程相同。当浏览器第一次对资源请求的时候,服务器返回的响应头中会携带 Etag 字段,该字段标注的是该资源的 Hash 地址,当浏览器再次访问该资源的时候,请求头中会携带 If-Match 字段,该字段携带的就是服务器返回来的 Hash 地址,当服务器接收到的时候,就回去判断该 Hash 地址如该资源当前的 Hash 地址是否相同,如果相同就返回 304 状态码,让浏览器直接去找缓存,如果不相同则跟普通请求资源的过程无异。

总结

HTTP 缓存的过程如下:

  1. 当浏览器请求资源的时候,服务器的响应头中会携带 Cache-Control 字段,首先判断 Cache-Control 是否为强缓存,或者缓存时间是否过期

  2. 如果不是强缓存,则直接走协商缓存,如果缓存时间过期,则也直接走协商缓存

  3. 协商缓存服务器会判断请求头中 If-Modified-Since 和 If-Match 是否与当前资源的最新修改日期或 Hash 标识相同,如果相同则直接返回 304 状态码,告诉浏览器去缓存位置拿取数据,如果不相同,则和普通请求资源一样,返回资源。

8.Cookie

因为 HTTP 是无状态的,所以谁去请求服务器,服务器是不知道的,所以通常情况下,我们可以通过设置 Cookie 来告知服务器是谁在使用。

Cookie 的存储只有 4KB,并且该存储是存在硬盘上的,存储量较小,这也是 Cookie 让人头疼一个问题。

第二个问题就是 Cookie 无法跨域,只能在同一个域名下,这块上面说的跨域问题有提到。

我们可以通过 Set-Cookie 来设置 Cookie,通常情况下是服务器端通过 Set-Cookie 字段来设置 Cookie,设置好之后,浏览器每次请求资源都会在请求头携带上通过 Cookie 字段将服务器传来的 Cookie 值带给服务器端。Cookie 是键值对的形式来保存的,可以设置很多个。

Cookie 的属性

max-age 和 expires 设置 cookie 的过期时间,当两者同时存在时,浏览器会优先选择 max-age 定义的时间。

Secure 只在 HTTPS 的时候发送发送 cookie

HTTPOnly 无法通过 document.cookie 访问,为了防止 XSS 攻击,禁止 JS 文件去访问 cookie。

Domain 设置 Cookie 的指定域名,如可以通过给 Cookie 设置域名,让该域名下所有的二级域名都可以使用该 Cookie 信息

Path 可以给服务器上的指定文档设置 Cookie,如果 path 为/的话,就代表该域名下所有路径都可以使用该 Cookie

Cookie 的安全性

除了上述的 HTTPOnly 用于防止 XSS 攻击外,在 CSRF 攻击上,Cookie 同样也有解决方案,即 SameSite。

SameSite 用来限制第三方 Cookie,从而减少安全风险。

SameSite 可以设置三个值:

  1. Strict:严格,完全禁止第三方 Cookie,跨站点时,任何情况下都不会发送 Cookie。换言之,只有当前网页的 URL 与请求目标一致,才会带上 Cookie。

  2. Lax:大多数情况也是不发送第三方 Cookie,但是导航到目标网址的 Get 请求除外。

  3. None:请求会自动携带上 Cookie。

如何将 Cookie 清除

  1. 在浏览器上手动清除。
  2. 将 Cookie 的时间设置为-1,当浏览器关闭后就会删除 Cookie。
  3. 将 max-age 设置为 0,浏览器会立即删除 Cookie,max-age=0 是指不能缓存,但在会话期间是可用的,浏览器会话关闭之前可以用 cookie 记录用户的信息。

9.HTTP 连接

HTTP 连接分为两种,一种是短连接,一种是长连接。

短连接

在 HTTP 协议 0.9/1.0 版本时,客户端和服务器之间的通讯还是非常简单的,就是很普通的“请求-应答”,即当客户端收到响应报文的时候,该 TCP 通道就关闭了。

但是我们知道当 TCP 通道开启和关闭的时候,要经过三次握手和四次挥手的过程,如果每次客户端收到响应报文后,都关闭该 TCP 通道,那每次在客户端请求资源的时候都会经历三次握手和四次挥手的过程,这样大大增加了请求资源的时间和成本,所以在 HTTP 协议 1.1 版本的时候,引入了长连接的概念。

长连接

长连接的思想很简单,就是将一个“请求-应答”均摊到多个“请求-应答”,因为 TCP 的开启和关闭很耗费时间,那我们就复用这个通道。

关联字段

客户端是默认使用长连接的,即在请求头中写上 Connection:keep-live。不过不管客户端是否显式要求长连接,如果服务器支持长连接,它总会在响应报文里放一个“Connection: keep-alive”字段,告诉客户端:“我是支持长连接的,接下来就用这个 TCP 一直收发数据吧”。

但是在有些时候,我们并不想应用长连接这个特性,那我们可以在请求头将 Connection 字段设置为 close,当服务器看到该字段为 close 的时候,就知道客户端要主动关闭连接,于是在响应报文里也加上这个字段,发送之后就调用 Socket API 关闭 TCP 连接。

值得记住的是,服务端是不会主动关闭长连接的。

10.队头阻塞

上一小节我们说到,HTTP 协议是“请求-应答”模式,当多个资源同时请求的时候,前面的请求会优先处理,后面的请求会等待,这样就会造成性能的下降。

那如何解决这种排队等待的问题呢?

  1. 并发连接

既然是一个一个的“请求-应答”,那客户端一下建立多个长连接,并发处理,用数量取代质量,这样话时间就大大缩短了。但是也不能让客户端无限的并发下去,这样对服务器是一种不小的伤害,所以 RFC2616 明确规定了客户端最多并发 2 个连接,但是浏览器觉得 2 个完全不能满足要求,所以都无视了这个规定,像谷歌浏览器就是 6 个并发连接,后来 RFC2616 也取消了 2 个的限制。

但并发连接所压榨出的性能也跟不上高速发展的互联网无止境的需求

  1. 域名分片

浏览器为了减轻服务器的压力,限制了并发的数量,但是如果同时请求的资源很多的话,并发连接的优化的速度也是微乎其微的。

既然并发的数量限制了,那我们可以增加多个域名,将域名都指向同一个服务器,这样的话处理的数量又增加了,这就是域名分片。

比如 content1.tomato.com,content2.toamto.com....

tomato.com 域名下可以分出非常多的二级域名,而它们都指向同样的一台服务器,能够并发的长连接数更多了,事实上也更好地解决了队头阻塞的问题。

11.数据协商

数据协商主要是客户端向服务端发送请求的时候,客户端向服务端声明这个请求拿到的数据格式和一些数据限制都是怎么样的,然后服务端会根据客户端发来的头信息返回响应的内容。

MIME type

为了下文的需要,在此先讲解一些 MIME type。

MIME:多用途互联网邮件扩展(Multipurpose Internet Mail Extensions),简称为 MIME。这块不重要毕竟是个概念,为什么这么叫不重要 😂😂😂

MIME 是个大类,HTTP 只是将其中一部分定义为了 body 数据的类型,这就是 MIME Type。

通常我们见到的 MIME Type 为一下几个:

  1. text:即文本格式的可读数据,我们最熟悉的应该就是 text/html 了,表示超文本文档,此外还有纯文本 text/plain、样式表 text/css 等。
  2. image:即图像文件,有 image/gif、image/jpeg、image/png 等。
  3. audio/video:音频和视频数据,例如 audio/mpeg、video/mp4 等。
  4. application:数据格式不固定,可能是文本也可能是二进制,必须由上层应用程序来解释。常见的有 application/json,application/javascript、application/pdf 等,另外,如果实在是不知道数据是什么类型,就会是 application/octet-stream,即不透明的二进制数据。

请求

Accept

Accept:指定相应的数据类型,数据类型会根据 MIME type 来指定,并用','隔开

Accept-Encoding:规定数据是怎样的一个编码方式,主要是限制服务端进行一个怎样的数据压缩,这个主要是从 Encoding type 中选取,同样也用','隔开表示多个。

Encoding type 的数据压缩格式通常只有一下几种:

  1. gzip:GNU zip 压缩格式,也是互联网上最流行的压缩格式;
  2. deflate:zlib(deflate)压缩格式,流行程度仅次于 gzip;
  3. br:一种专门为 HTTP 优化的新压缩算法(Brotli)。数据类型使用的头字段

Accept-Language:规定返回信息的语言

User-Agent:用来表示浏览器的一些设备信息

Content

我们在请求时,只在请求头中设置 Content-Type 这个字段,表示客户端发送给服务端的数据是什么样的类型,主要应用于 POST 请求。

在此处我们讲解在请求头中最常见的 Content-Type 字段,主要是用于表单提交告诉服务端传递给它的数据是什么格式:

  1. application/x-www-form-urlencoded
  • 以键值对的形式呈现,并用&将多个键值对相连
  • 字符以 URL 编码方式编码
  1. multipart/form-data
  • 用 boundary 将数据进行分割,boundary 的值是由浏览器指定
  • 每部分数据都含有 HTTP 头部描述信息,并在最后以--作为结束

响应

Content

Content-Type:根据 Accept 中的数据类型,选择其中一种数据类型来作为该数据的数据类型返回给客户端。

Content-Encoding:服务端具体用了那种数据压缩方式、

Content-Language:指定返回数据的语言类型

12.HTTP 的重定向和跳转

HTTP 的重定向是指,当客户端请求资源的 URL 转移到另外一个 URL 的时候,服务端要告诉浏览器该资源新的 URL。

Location

此字段只是响应字段,只能服务器去编写,并且该字段只在状态码为 301 和 302 的时候才生效,该字段携带的内容就是该资源新的 URL。

301

301 代表的是永久重定向,浏览器看到 301,就知道原来的 URI“过时”了,就会做适当的优化。比如历史记录、更新书签,下次可能就会直接用新的 URI 访问,省去了再次跳转的成本。搜索引擎的爬虫看到 301,也会更新索引库,不再使用老的 URI,并且再次访问时会直接从缓存中读取数据。所以尽量不要使用 301 状态码,除非绝对肯定之后不再会更改。

302

302 代表的是临时重定向,即原来的 URI 仍然有效,现在新的 URI 只是暂时生效而已,浏览器或者爬虫看到 302,会认为原来的 URI 仍然有效,但暂时不可用,所以只会执行简单的跳转页面,不记录新的 URI,也不会有其他的多余动作,下次访问还是用原 URI。

13.HTTP 的特性和优缺点

HTTP 的特点

  1. 灵活可扩展

    HTTP 最开始诞生的时候就是比较简单的,而且本着开放的精神,只是对报文进行了基本的定义,报文里的各个组成部分都没有做严格的语法语义限制,可以由开发者任意定制。慢慢的随着互联网技术的发展,逐步增加了请求方法,状态码等,body 中也不再只限制于 TEXT 或 HTML,增加了图片和音视频等任意数据。

  2. 可靠传输

    因为 HTTP 协议是基于 TCP/IP 的,TCP 本身就是可靠的传输协议,所以 HTTP 也是可靠的。

  3. 应用层协议

    这个话不多说。

  4. 请求 - 应答

    HTTP 协议使用的是请求 - 应答通信模式。请求 - 应答,简单来说就是一发一收。

    请求 - 应答模式也明确了 HTTP 协议里通信双方的定位,永远是请求方先发起连接和请求,是主动的,而应答方只有在收到请求后才能答复,是被动的,如果没有请求时不会有任何动作。

    当然,请求方和应答方的角色也不是绝对的,在浏览器 - 服务器的场景里,通常服务器都是应答方,但如果将它用作代理连接后端服务器,那么它就可能同时扮演请求方和应答方的角色。

    HTTP 的请求 - 应答模式也恰好契合了传统的 C/S(Client/Server)系统架构,请求方作为客户端、应答方作为服务器。所以,随着互联网的发展就出现了 B/S(Browser/Server)架构,用轻量级的浏览器代替笨重的客户端应用,实现零维护的“瘦”客户端,而服务器则摈弃私有通信协议转而使用 HTTP 协议。

    此外,请求 - 应答模式也完全符合 RPC(Remote Procedure Call)的工作模式,可以把 HTTP 请求处理封装成远程函数调用,导致了 WebService、RESTful 和 gPRC 等的出现。无状态

  5. 无状态

    如何理解这个“无状态”,在整个 HTTP 协议中没有规定认可状态,客户端和服务端之间通信其实两者都是处于“懵逼”的状态,建立连接前两者互不知情,每次收发的报文也都是互相独立的,没有任何的联系。收发报文也不会对客户端或服务器产生任何影响,连接后也不会要求保存任何信息。

  6. 其他特点

    除了以上的五大特点,其实 HTTP 协议还可以列出非常多的特点,例如传输的实体数据可缓存可压缩、可分段获取数据、支持身份认证、支持国际化语言等。但这些并不能算是 HTTP 的基本特点,因为这都是由第一个“灵活可扩展”的特点所衍生出来的。

HTTP 的优缺点

其实上文有说道 HTTP 的特点,那这次就将这些特点进行分类,看看哪些特点好,哪些特点不好。

首先 HTTP 最大的好处就是简单、灵活、易于扩展,以报文为例,上面的单词基本都以简单和通俗易懂为主,这对新手来说方便记忆,并且传输种类多样。

第二个好处就是应用广泛、环境成熟,这个很重要,就以前端来说为什么新的框架和小的框架用的人很少,就是因为生态和成熟度决定的,越成熟说明稳定性安全性都得到了可靠的验证,这对互联网安全来说尤为重要。

还有两把双刃剑特性,第一把就是无状态,无状态的好处有很多,因为这个特性,服务器就没有记忆的能力,从而减轻了服务器的负担,能够把更多的 CPU 和内存用来对外提供服务。而且,“无状态”也表示服务器都是相同的,没有“状态”的差异,所以可以很容易地组成集群,让负载均衡把请求转发到任意一台服务器,不会因为状态不一致导致处理出错,使用“堆机器”的“笨办法”轻松实现高并发高可用。无状态的缺点呢,当你登录一个电商或者一个需要用户的网站时,如订单和支付等操作都需要极强的身份信息认证,如果不解决无状态的这个问题,那服务器怎么会知道你是谁呢?那这样岂不是谁都可以获取到你的信息。

第二把双刃剑就是明文传输,明文传输最好的点就是可以清晰明了的知道报文中显示的是什么内容,为开发者调试提供了极大地方便。缺点就是带来了安全隐患。典型的就是 WIFI 陷阱利用 HTTP 明文传输的缺点,诱导你连上热点,然后疯狂抓你所有的流量,从而拿到你的敏感信息。

还要一个缺点就是之前所讲的队头堵塞,在这就不过多的讲解了。

14.HTTPS

上一节讲述了 HTTP 的特性和优缺点,在缺点中,我们可以通过 Cookie 来解决 HTTP 的无状态的缺点,但是明文传输和不安全是解决不了的,所以就出现了 HTTPS 协议。

HTTPS 和 HTTP 在特性上只是两个协议名称不同和端口号不同,HTTP 默认的端口号是 80,HTTPS 默认的端口号是 443,剩下的报文,传输特性等都是一样的。

HTTPS 究竟安全在哪里?

其实 HTTPS 真正安全的并不是其本身,而是在其'S'身上,普通的 HTTP 协议是建立在 TCP/IP 上的,而 HTTPS 是建立在 SSL/TLS 上的,因为 SSL/TLS 是安全的协议,所以说 HTTPS 是安全的。

SSL/TLS

SSL 即安全套接层(Secure Sockets Layer),在 OSI 模型中处于第 5 层(会话层),由网景公司于 1994 年发明,有 v2 和 v3 两个版本,而 v1 因为有严重的缺陷从未公开过。

TLS 是 SSL 的 v3 版本的升级版,只是换了个名字,实际 TLS 就是 SSL 的 v3.1 版本。TLS 由记录协议、握手协议、警告协议、变更密码规范协议、扩展协议等几个子协议组成,综合使用了对称加密、非对称加密、身份认证等许多密码学前沿技术。

所以 SSL/TLS 要比 TCP/IP 要安全很多。

15.TLS1.2

TLS 协议的组成

  1. 记录协议(Record Protocol)规定了 TLS 收发数据的基本单位:记录(record)。它有点像是 TCP 里的 segment,所有的其他子协议都需要通过记录协议发出。但多个记录数据可以在一个 TCP 包里一次性发出,也并不需要像 TCP 那样返回 ACK。

  2. 警报协议(Alert Protocol)的职责是向对方发出警报信息,有点像是 HTTP 协议里的状态码。比如,protocol_version 就是不支持旧版本,bad_certificate 就是证书有问题,收到警报后另一方可以选择继续,也可以立即终止连接。

  3. 握手协议(Handshake Protocol)是 TLS 里最复杂的子协议,要比 TCP 的 SYN/ACK 复杂的多,浏览器和服务器会在握手过程中协商 TLS 版本号、随机数、密码套件等信息,然后交换证书和密钥参数,最终双方协商得到会话密钥,用于后续的混合加密系统。

  4. 变更密码规范协议(Change Cipher Spec Protocol),它非常简单,就是一个“通知”,告诉对方,后续的数据都将使用加密保护。那么反过来,在它之前,数据都是明文的。

TLS 握手过程

  1. 在 TCP 建立连接之后,浏览器会首先发一个“Client Hello”消息,也就是跟服务器“打招呼”。里面有客户端的版本号、支持的密码套件,还有一个随机数(Client Random),用于后续生成会话密钥。
什么是密码套件

HTTPS 体现安全的特性有一点就是机密性,那如何体现机密性,最常用的一点就是加密,解开这个秘密的钥匙就是秘钥。加密算法都是公开的但是秘钥是绝对保密的,所以通过秘钥的使用方法,通常加密分为两部分,一个是对称加密,另一个是非对称加密。

密码套件就是各个对称算法的组合,常用的是 GCM、CCM 和 Poly1305。

  1. 服务器收到“Client Hello”后,会返回一个“Server Hello”消息。把版本号对一下,也给出一个随机数(Server Random),然后从客户端的列表里选一个作为本次通信使用的密码套件。并且服务器会向客户端发送一个证书,已表明自己的身份。证书发送后,服务器会接着发送“Server Key Exchange”消息,里面是公钥(Server Params),用来实现密钥交换算法,再加上自己的私钥签名认证。

  2. 在客户端接收到这个证书和签名后,进行验证,验证通过后根据服务器选择的密码套件生成一个公钥 Client Params,然后将这个公钥参数用“Client Key Exchange”传递给服务器。然后通过 Server Params 和 Client Params 生成一个随机数(Pre Random)。

  3. 服务器根据客户端传过来的 Client Params 生成 Pre Random,现在客户端和服务器都拥有了 Server Params,Client Params 和 Pre Random,接下来客户端和服务端都根据这三个参数生成用于加密会话的主密钥,叫“Master Secret”。

  4. 生成主密钥后,还会通过一个属性(PRF)生成客户端发送用的会话密钥(client_write_key)、服务器发送用的会话密钥(server_write_key)等等,避免只用一个密钥带来的安全隐患。

  5. 客户端的会话密钥和服务端的会话密钥生成后,客户端发一个“Change Cipher Spec”,然后再发一个“Finished”消息,把之前所有发送的数据做个摘要,再加密一下,让服务器做个验证。服务器也是同样的操作,发“Change Cipher Spec”和“Finished”消息,双方都验证加密解密 OK,此时握手正式结束。

16.TLS1.3 的优势

强化安全

在 TLS1.2 过去十年的时间了,积攒了大量的经验,陆续发现了很多的漏洞和加密算法的弱点,所以 TLS1.3 就在协议里修补了这些不安全因素。

所以在 TLS1.3 中精简了部分的密码套件,最后只留下来了 5 个:

  • TLS_AES_128_GCM_SHA256
  • TLS_AES_256_GCM_SHA384
  • TLS_CHACHA20_POLY1305_SHA256
  • TLS_AES_128_GCM_SHA256
  • TLS_AES_128_GCM_8_SHA256

废除了 RSA 和 DH 密钥交换算法。

性能提升

TLS1.3 性能的提升主要是在握手过程中,从 TLS1.2 中就可以看出,握手协议相当的复杂。所以优化握手的步骤就能节省很大时间。

现在因为密码套件大幅度简化,也就没有必要再像以前那样走复杂的协商流程了。TLS1.3 压缩了以前的“Hello”协商过程,删除了“Key Exchange”消息,把握手时间减少到了“1-RTT”,效率提高了一倍。

其实具体的做法还是利用了扩展。客户端在“Client Hello”消息里直接用“supported_groups”带上支持的曲线,比如 P-256、x25519,用“key_share”带上曲线对应的客户端公钥参数,用“signature_algorithms”带上签名算法。

服务器收到后在这些扩展里选定一个曲线和参数,再用“key_share”扩展返回服务器这边的公钥参数,就实现了双方的密钥交换,后面的流程就和 1.2 基本一样了。

17.HTTPS 的优化

HTTPS 在连接的时候,首先是 TCP 的三次握手,接着还会执行一次 TLS 连接,在 TLS 连接时会计算加密算法,所以会比 HTTP 连接慢一些。那接下来看一下如何提高 HTTPS 连接的速度。

1. 硬件优化

首先选用更快的 CPU,最好还内建 AES 优化,这样即可以加速握手,也可以加速传输。

其次可以选择“SSL 加速卡”,加解密时调用它的 API,让专门的硬件来做非对称加解密,分担 CPU 的计算压力。

但是“SSL 加速卡”也有问题,对计算算法,还有不能灵活定制都已一定的限制,所以就出现了第三种加速方式,“SSL 加速服务器”,用专门的服务器集群来彻底“卸载”TLS 握手时的加密解密计算,性能自然要比单纯的“加速卡”要强大的多。

2. 软件优化

软件升级有两种方式,第一种是软件本身升级,第二种就是协议升级。

软件升级就不需多说了,就是把正在使用的软件升级,因为软件升级本身就会有一些优化,所以这种也是最容易的优化。

协议优化

协议优化就是将核心的 TLS 进行优化,因为 HTTPS 本身“慢”的点就是 TLS。对其优化的话首先就是将 TLS 升级到 1.3 版本。

如果暂时不能升级到 1.3 版本的话,那就要在握手时使用的密钥交换协议应当尽量选用椭圆曲线的 ECDHE 算法。因为它不仅运算速度快,安全性高,还支持“False Start”,能够把握手的消息往返由 2-RTT 减少到 1-RTT,达到与 TLS1.3 类似的效果。

另外,椭圆曲线也要选择高性能的曲线,最好是 x25519,次优选择是 P-256。对称加密算法方面,也可以选用“AES_128_GCM”,它能比“AES_256_GCM”略快一点点。

证书优化

证书优化点有两个:第一个是证书传输,第二个是证书验证。

服务器的证书可以选择椭圆曲线(ECDSA)证书而不是 RSA 证书,因为 ECDSA 证书要小很多,可以减少很多带宽。

客户端的证书验证其实是个很复杂的操作,除了要公钥解密验证多个证书签名外,因为证书还有可能会被撤销失效,客户端有时还会再去访问 CA,下载 CRL 或者 OCSP 数据,这又会产生 DNS 查询、建立连接、收发数据等一系列网络通信,增加好几个 RTT。

CRL(Certificate revocation list,证书吊销列表)由 CA 定期发布,里面是所有被撤销信任的证书序号,查询这个列表就可以知道证书是否有效。但是 CRL 是定期发布的,并且会很大,通常有几 MB,这样每次查询会有很占用资源,影响效率。现在都是使用 OCSP Stapling”(OCSP 装订),它可以让服务器预先访问 CA 获取 OCSP 响应,然后在握手时随着证书一起发给客户端,免去了客户端连接 CA 服务器查询的时间。

会话复用

会话复用有些类似于缓存,它是将 TLS 过程中算出来的主密钥进行重复使用。

会话复用分为两种,第一种是“Session ID”,就是客户端和服务器首次连接后各自保存一个会话的 ID 号,内存里存储主密钥和其他相关的信息。当客户端再次连接时发一个 ID 过来,服务器就在内存里找,找到就直接用主密钥恢复会话状态,跳过证书验证和密钥交换,只用一个消息往返就可以建立安全通信。

第二种是会话票证(Session Ticket),它有点类似 HTTP 的 Cookie,存储的责任由服务器转移到了客户端,服务器加密会话信息,用“New Session Ticket”消息发给客户端,让客户端保存。重连的时候,客户端使用扩展“session_ticket”发送“Ticket”而不是“Session ID”,服务器解密后验证有效期,就可以恢复会话,开始加密通信。

但是上述两种方案都存在一个问题,就是它们都只能实现 1-RTT,而 TLS1.3 更进一步实现了“0-RTT”,原理和“Session Ticket”差不多,但在发送 Ticket 的同时会带上应用数据(Early Data),免去了 1.2 里的服务器确认步骤,这种方式叫“Pre-shared Key”,简称为“PSK”。

18.HTTP2

HTTP2.0 主要优化的点主要是以下几种:

  1. 头部压缩
  2. 信道复用
  3. 二进制分帧传输
  4. 服务器推送

头部压缩

在 HTTP1.1 版本中,压缩只是在 Content-Encoding 中,即服务器对返回资源的压缩,但是报文中 Header 却被忽视了。

我们开发时可以发现,报文中 Header 部分也携带了大量的字段,如 Accept,User-Agent,Cookie 等等等等,并且有一个点是这些字段都是重复存在的,非常浪费性能。

所以在 HTTP2.0 中,运用了 HPACK 算法进行了头部压缩,在客户端和服务器两端建立“字典”,用索引号表示重复的字符串,还釆用哈夫曼编码来压缩整数和字符串,可以达到 50%~90% 的高压缩率。

信道复用

在 HTTP1.1 版本中,我们说过 HTTP 有个非常影响效能的缺点,就是队头堵塞。虽然我们运用了长链接,域名分片,增加并发连接,但是这些也是无可奈何的解决方案,没有从根本上去解决。

在 HTTP2.0 中,增加了一个信道复用这个特点,即可以复用 TCP/IP 通道,平行发送请求。

感觉是不是和 HTTP1.1 中的长链接很像,其实是有很大不同的,因为在 HTTP1.1 中,发送请求都是顺序发送,即在前一个请求-应答结束后,才会执行下一个请求-应答,虽然长链接也是复用了 TCP/IP 通道,但是在通道中这些请求还是按顺序发送的。但是在 HTTP2.0 中,通道内的请求是并行发送的,这样明显解决了串行问题,大大增加了效率。

二进制分帧传输

除了上述所讲的信道复用外,还有一个改进就是二进制分帧传输。在 HTTP1.1 中,报文中全是简单明了的纯文本形式,这种虽然便于开发和读取,但是程序在处理起来很麻烦,而且还要编译,大大降低了效能。

所以在 HTTP2.0 中没有去延续 HTTP1.1 中这个特点,而是将其全面转换成了二进制模式,对于计算机来说全是‘0’和‘1’,不要太美好。改成了二进制,对其优化起来就容易多了,分帧传输的想法悠然而生。

分帧传输是把原有的 Header-Body 特性打散,变成一个个小的二进制帧,用“HEADERS”帧存放头数据、“DATA”帧存放实体数据,这块的步骤就是化整为零,然后在服务器端再化零为整。每个帧从客户端到服务端,再从服务端到客户端,这种双向传输称之为流,并将每个流标记上一个 ID,每个流中都流动着带有顺序的小碎片即帧,并且每个流中可以并发多个碎片,这就是上面所说的信道复用。

在“流”的层面上看,消息是一些有序的“帧”序列,而在“连接”的层面上看,消息却是乱序收发的“帧”。多个请求 / 响应之间没有了顺序关系,不需要排队等待,也就不会再出现“队头阻塞”问题,降低了延迟,大幅度提高了连接的利用率。为了更好地利用连接,加大吞吐量,HTTP/2 还添加了一些控制帧来管理虚拟的“流”,实现了优先级和流量控制。

服务器推送

HTTP/2 还在一定程度上改变了传统的“请求 - 应答”工作模式,服务器不再是完全被动地响应请求,也可以新建“流”主动向客户端发送消息。比如,在浏览器刚请求 HTML 的时候就提前把可能会用到的 JS、CSS 文件发给客户端,减少等待的延迟,这被称为“服务器推送”(Server Push,也叫 Cache Push)。

其他特性

除了上述讲解的优势外,HTTP2.0 还增加了安全性,因为 HTTPS 的崛起,各大浏览器都明确规定支持加密的 HTTP2.0,所以现在说的 HTTP2.0 都是使用的 HTTPS 的协议,跑在 TLS 上。但是 HTTP2.0 还需要向下兼容,需要明文传输,不强制使用加密通信,不过格式还是二进制,只是不需要解密。

所以为了兼容性,HTTP2.0 定义了两个字符串来区分明文和加密两种形式,“h2”表示的是加密的 HTTP2.0,“h2c”表示的是明文的 HTTP2.0,这样就解决了兼容性的问题。

我们来看一下 HTTP1.1,HTTPS,HTTP2.0 的协议栈。 通过上图我们可以一目了然的看到,各个协议之间的不同。

参考

透视 HTTP 协议

HTTP 协议原理+实践 Web 开发工程师必学

(建议精读)HTTP 灵魂之问,巩固你的 HTTP 知识体系

最后

看完此文章,是不是觉得挺干的,我自己都觉得很干,因为 HTTP 协议很抽象,但是作为 WEB 工程师的我们,每天都和 HTTP 打交道,不了解 HTTP 的特性确实很不应该,并且确实对我们开发是很有帮助的。在我这写篇文章的时候就遇到了一个 BUG 和 HTTP 协议有关,有一个 FEEDBACK,客户那边催的很急,虽然不影响使用但是谁让客户是甲方呢!!!这个问题是后端返回客户端一个图片地址,然后有一个查看原图的功能,但是点击查看原图时发现只是下载图片。这个 FEEDBACk 提给了前端,认为是我们这边有问题。我查看了许久,没有发现任何问题,应用起来发现有些情况下是没问题的,有些情况下就下载,最开始我以为是跟图片的格式有关系,并反复验证,发现并不是图片的问题,那问题出现在了哪里??我和后端还有另外一个前端都觉得不可思议,后来另外一个前端发现了不同,这个不同点就在 HTTP 的报文中,而且是一个不太关注的字段 Server,发现正常情况下 Server 和 BUG 情况下的 Server 是不同的,所以找到了这个 BUG 的关键问题,服务器的不同,后来将这个问题反馈给了运维,最后得到了解决。🤣🤣🤣 经过这件事,更加让我觉得 HTTP 协议的重要性。

后语

觉得还可以的,麻烦走的时候能给点个赞,大家一起学习和探讨!

还可以关注我的博客希望能给我的github上点个Start,小伙伴们一定会发现一个问题,我的所有用户名几乎都与番茄有关,因为我真的很喜欢吃番茄❤️!!!

想跟车不迷路的小伙还希望可以关注公众号 前端老番茄 或者扫一扫下面的二维码👇👇👇。 我是一个编程界的小学生,您的鼓励是我不断前进的动力,😄希望能一起加油前进。