Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

超文本传输协议-HTTP #114

Open
yanyue404 opened this issue Dec 30, 2019 · 0 comments
Open

超文本传输协议-HTTP #114

yanyue404 opened this issue Dec 30, 2019 · 0 comments

Comments

@yanyue404
Copy link
Owner

HTTP

超文本传输协议(英语:HyperText Transfer Protocol,缩写:HTTP)是一种用于分布式、协作式和超媒体信息系统的应用层协议。HTTP 是万维网的数据通信的基础。

HTTP 是一个客户端(用户)和服务端(网站)之间请求和应答的标准,通常使用 TCP 协议。通过使用网页浏览器、网络爬虫或者其它的工具,客户端发起一个 HTTP 请求到服务器上指定端口(默认端口为 80)。我们称这个客户端为用户代理程序。应答的服务器上存储着一些资源,比如 HTML 文件和图像。我们称这个应答服务器为源服务器。(在用户代理和源服务器中间可能存在多个“中间层”,比如代理服务器、网关或者隧道)

协议:规定了客户端与服务器双方必须遵守约定好的数据传输格式

一个完成的 HTTP 请求

  • 域名解析(此处涉及 DNS 的寻址过程)
  • 发起 TCP 的 3 次握手
  • 建立 TCP 连接后发起 http 请求
  • 服务器响应 http 请求,浏览器得到 html 代码
  • 浏览器解析 html 代码,并请求 html 代码中的资源(如 js、css、图片等,此处可能涉及 HTTP 缓存)
  • 浏览器对页面进行渲染呈现给用户(此处涉及浏览器的渲染原理)

TCP 三次握手

传输控制协议(英语:Transmission Control Protocol,缩写:TCP)是一种面向连接的、可靠的、基于字节流的传输层通信协议。

TCP 用三次握手过程创建一个连接。在连接创建过程中,很多参数要被初始化,例如序号被初始化以保证按序传输和连接的强壮性。

TCP 连接的正常创建
一对终端同时初始化一个它们之间的连接是可能的。但通常是由一端打开一个套接字(socket)然后监听来自另一方的连接,这就是通常所指的被动打开(passive open)。服务器端被被动打开以后,用户端就能开始创建主动打开(active open)。

  • 客户端通过向服务器端发送一个 SYN 来创建一个主动打开,作为三次握手的一部分。客户端把这段连接的序号设定为随机数 A。
  • 服务器端应当为一个合法的 SYN 回送一个 SYN/ACK。ACK 的确认码应为 A+1,SYN/ACK 包本身又有一个随机产生的序号 B。
  • 最后,客户端再发送一个 ACK。此时包的序号被设定为 A+1,而 ACK 的确认码则为 B+1。当服务端收到这个 ACK 的时候,就完成了三次握手,并进入了连接创建状态。

协议内容

请求(Request)

客户端发送一个 HTTP 请求到服务端的格式:

  • 请求行
  • 请求头
  • 请求体

请求行大概长这样 GET /images/logo.gif HTTP/1.1,基本由请求方法、URL、协议版本组成。

请求方法

  • GET:向指定的资源发出“显示”请求。该方法应该只用在读取数据,而不应当被用于产生"副作用”的操作中,例如在网络应用程序中。其中一个原因是 GET 可能会被网络爬虫等随意访问。GET 上要在 url 之外带一些参数就只能依靠 url 上附带 querystring。
  • HEAD:与 GET 方法一样,都是向服务器发出指定资源的请求。只不过服务器将不传回资源的本文部分。它的好处在于,使用这个方法可以在不必传输全部内容的情况下,就可以获取其中“关于该资源的信息”(元信息或称元数据)。
  • PUT:向指定资源位置上传其最新内容。PUT 方法是幂等的方法,一般用于整体资源的更新。
  • PATCH:与 PUT 请求类似,同样用于资源的更新。PATCH 一般用于资源的部分更新。当资源不存在时,PATCH 会创建一个新的资源,而 PUT 只会对已经存在的资源更新。
  • POST:向指定资源提交数据,请求服务器进行处理(例如提交表单或者上传文件)。数据被包含在请求本文中。这个请求可能会创建新的资源或修改现有资源,或二者皆有。每次提交,表单的数据被浏览器用编码到 HTTP 请求的 body 里。浏览器发出的 POST 请求的 body 主要有有两种格式,一种是 application/x-www-form-urlencoded 用来传输简单的数据,大概就是key1=value1&key2=value2这样的格式。另外一种是传文件,会采用 multipart/form-data 格式。采用后者是因为前者对于文件这种二进制的数据非常低效。
  • DELETE:请求服务器删除 Request-URI 所标识的资源

Post 和 Get 的区别?

常见状态码

状态代码的第一个数字代表当前响应的类型:

  • 1xx 消息——请求已被服务器接收,继续处理
  • 2xx 成功——请求已成功被服务器接收、理解、并接受
    • 200 OK,表示从客户端发来的请求在服务器端被正确处理
  • 3xx 重定向——需要后续操作才能完成这一请求
    • 302 found,临时性重定向,表示资源临时被分配了新的 URL
    • 303 see other,表示资源存在着另一个 URL,应使用 GET 方法获取资源
  • 4xx 请求错误——请求含有词法错误或者无法被执行就、8
    • 400 bad request,请求报文存在语法错误
    • 401 unauthorized,表示发送的请求需要有通过 HTTP 认证的认证信息
    • 403 forbidden,表示对请求资源的访问被服务器拒绝
    • 404 not found,表示在服务器上没有找到请求的资源
  • 5xx 服务器错误——服务器在处理某个正确请求时发生错误
    • 500 internal sever error,表示服务器端在执行请求时发生了错误
    • 501 Not Implemented,表示服务器不支持当前请求所需要的某个功能
    • 503 service unavailable,表明服务器暂时处于超负载或正在停机维护,无法处理请求

通用首部

  • Cache-Control 控制缓存的行为

常见请求头

  • Accept-Encoding: gzip, deflate, br 浏览器申明自己接收的编码方法,通常指定压缩方法
  • Connection:keep-alive  当一个网页打开完成后,客户端和服务器之间用于传输 HTTP 数据的 TCP 连接不会关闭,如果客户端再次访问这个服务器上的网页,会继续使用这一条已经建立的连接。close  代表一个 Request 完成后,客户端和服务器之间用于传输 HTTP 数据的 TCP 连接会关闭, 当客户端再次发送 Request,需要重新建立 TCP 连接。
  • Cookie:是用来存储一些用户信息以便让服务器辨别用户身份的,当用户登录后就会在客户端产生一个 cookie 来存储相关信息,这样浏览器通过读取 cookie 的信息去服务器上验证并通过后会判定你是合法用户,从而允许查看相应网页
  • User-Agent:客户端使用的操作系统和浏览器的名称和版本。
  • Host:服务器的域名
  • If-Modified-Since:本地资源未修改返回 304(比较时间)
  • If-None-Match:本地资源未修改返回 304(比较标记

常见响应头

  • ETag: 当前资源的标识
  • Last-Modified:该资源最后被修改的时间
  • Set-Cookie:设置 Cookie
  • Date:服务端发送资源时的服务器时间
  • Access-Control-Allow-Origin:指定哪些网站可以跨域资源共享

HTTP2

HTTP/2 相比于 HTTP/1,可以说是大幅度提高了网页的性能。

  • 新的二进制格式(Binary Format),HTTP1.x 的解析是基于文本。基于文本协议的格式解析存在天然缺陷,文本的表现形式有多样性,要做到健壮性考虑的场景必然很多,二进制则不同,只认 0 和 1 的组合。基于这种考虑 HTTP2.0 的协议解析决定采用二进制格式,实现方便且健壮。
  • 多路复用(MultiPlexing),即连接共享,即每一个 request 都是是用作连接共享机制的。一个 request 对应一个 id,这样一个连接上可以有多个 request,每个连接的 request 可以随机的混杂在一起,接收方可以根据 request 的 id 将 request 再归属到各自不同的服务端请求里面。
  • header 压缩,如上文中所言,对前面提到过 HTTP1.x 的 header 带有大量信息,而且每次都要重复发送,HTTP2.0 使用 encoder 来减少需要传输的 header 大小,通讯双方各自 cache 一份 header fields 表,既避免了重复 header 的传输,又减小了需要传输的大小。
  • 服务端推送(server push),同 SPDY 一样,HTTP2.0 也具有 server push 功能。

HTTPS

HTTPS 的主要作用是在不安全的网络上创建一个安全信道,并可在使用适当的加密包和服务器证书可被验证且可被信任时,对窃听和中间人攻击提供合理的防护。

HTTP 不是安全的,所有传输的内容都是明文,而且攻击者可以通过监听和中间人攻击等手段,获取网站帐户和敏感信息等。HTTPS 的设计可以防止前述攻击,在正确配置时是安全的。

HTTPS 是有两部分组成:HTTP + SSL/TLS,也就是在 HTTP 上又加了一层处理加密信息的协议。服务端和客户端的信息传输都会通过 TLS(传输层安全性协议) 进行加密,所以传输的数据都是加密后的数据。默认端口为 443。

HTTPS 和 HTTP 协议相比提供了:

  • 数据完整性:内容传输经过完整性校验
  • 数据隐私性:内容经过对称加密,每个连接生成一个唯一的加密密钥
  • 身份认证:第三方无法伪造服务端(客户端)身份

浏览器缓存

web 缓存

在 Web 应用领域,Web 缓存大致可以分为以下几种类型:

  • 数据库数据缓存
  • 服务器端缓存
  • 代理服务器缓存
  • CDN 缓存
  • 浏览器端缓存
  • Web 应用层缓存

浏览器缓存

当一个客户端请求 web 服务器, 请求的内容可以从以下几个地方获取:服务器、浏览器缓存中或缓存服务器中。这取决于服务器端输出的页面信息。

可以缓存的文件类型

页面文件有三种缓存状态

  • 最新的:选择不缓 M 存页面,每次请求时都从服务器获取最新的内容
  • 未过期的:在给定的时间内缓存文件,如果访问页面时资源过期则去服务器请求,否则将读取本地的缓存,这 o9 样可以提高浏览速度
  • 过期的:也就是陈旧的页面,当请求这个页面时,必须进行重新获取

浏览器缓存机制

很多时候,大家倾向于将浏览器缓存简单地理解为“HTTP 缓存”。但事实上,浏览器缓存机制有四个方面,它们按照获取资源时请求的优先级依次排列如下:

  1. Memory Cache
  2. Service Worker Cache
  3. HTTP Cache
  4. Push Cache

大家对 HTTP Cache(即 Cache-Control、expires 等字段控制的缓存)应该比较熟悉,如果对其它几种缓存可能还没什么概念,我们可以先来看一张线上网站的 Network 面板截图:

大家注意一下非数字——即形如“(from xxx)”这样的描述——对应的资源,这些资源就是我们通过缓存获取到的。其中,“from memory cache”对标到 Memory Cache 类型,“from ServiceWorker”对标到 Service Worker Cache 类型。至于 Push Cache,这个比较特殊,是 HTTP2 的新特性。HTTP 缓存是最主要、最具有代表性的缓存策略。

无法被浏览器缓存的请求

  • HTTP 信息头中包含 Cache-Control:no-cache,pragma:no-cache(HTTP1.0),或 Cache-Control:max-age=0 等告诉浏览器不用缓存的请求
  • 需要根据 Cookie,认证信息等决定输入内容的动态请求是不能被缓存的
  • 经过 HTTPS 安全加密的请求(有人也经过测试发现,ie 其实在头部加入 Cache-Control:max-age 信息,firefox 在头部加入 Cache-Control:Public 之后,能够对 HTTPS 的资源进行缓存,参考《HTTPS 的七个误解》)
  • POST 请求无法被缓存
  • HTTP 响应头中不包含 Last-Modified/Etag,也不包含 Cache-Control/Expires 的请求无法被缓存

HTTP 缓存机制

HTTP 缓存是我们日常开发中最为熟悉的一种缓存机制。它又分为强缓存和协商缓存。优先级较高的是强缓存,在命中强缓存失败的情况下,才会走协商缓存。

强缓存

强缓存是利用 http 头中的 Expires 和 Cache-Control 两个字段来控制的。强缓存中,当请求再次发出时,浏览器会根据其中的 expires 和 cache-control 判断目标资源是否“命中”强缓存,若命中则直接从缓存中获取资源,不会再与服务端发生通信。

其中 Expires 是一个时间戳,当服务器返回响应时,在 Response Headers 中将过期时间写入 expires 字段。

expires: Wed, 11 Sep 2019 16:12:18 GMT

接下来如果我们试图再次向服务器请求资源,浏览器就会先对比本地时间和 expires 的时间戳,如果本地时间小于 expires 设定的过期时间,那么就直接去缓存中取这个资源。

由于客户端时间与服务器端可能存在的时间差异,HTTP1.1 新增了 Cache-Control 字段来完成 expires 的任务。使用相对时间的方式进行标记。

cache-control: max-age=31536000

在 Cache-Control 中,我们通过 max-age 来控制资源的有效期,max-age 是 31536000 秒,它意味着该资源在 31536000 秒以内都是有效的。

协商缓存

协商缓存是浏览器与服务器合作之下的缓存策略,是否命中缓存依赖于浏览器与服务器的通信结果。

协商缓存机制下,浏览器需要向服务器去询问缓存的相关信息,进而判断是重新发起请求、下载完整的响应,还是从本地获取缓存的资源。

如果服务端提示缓存资源未改动(Not Modified),资源会被重定向到浏览器缓存,这种情况下网络请求对应的状态码是 304(如下图)。

协商缓存的实现:从 Last-Modified 到 Etag

Last-Modified 是一个时间戳,如果我们启用了协商缓存,它会在首次请求时随着 Response Headers 返回:

Last-Modified: Fri, 27 Oct 2017 06:35:57 GMT

随后我们每次请求时,会带上一个叫 If-Modified-Since 的时间戳字段,它的值正是上一次 response 返回给它的 last-modified 值:

If-Modified-Since: Fri, 27 Oct 2017 06:35:57 GMT

服务器接收到这个时间戳后,会比对该时间戳和资源在服务器上的最后修改时间是否一致,从而判断资源是否发生了变化。如果发生了变化,就会返回一个完整的响应内容,并在 Response Headers 中添加新的 Last-Modified 值;否则,返回如上图的 304 响应,Response Headers 不会再添加 Last-Modified 字段。

使用 Last-Modified 存在一些弊端,这其中最常见的就是这样两个场景:

  • 我们编辑了文件,但文件的内容没有改变。服务端并不清楚我们是否真正改变了文件,它仍然通过最后编辑时间进行判断。因此这个资源在再次被请求时,会被当做新资源,进而引发一次完整的响应——不该重新请求的时候,也会重新请求。

  • 当我们修改文件的速度过快时(比如花了 100ms 完成了改动),由于 If-Modified-Since 只能检查到以秒为最小计量单位的时间差,所以它是感知不到这个改动的——该重新请求的时候,反而没有重新请求了。

这两个场景其实指向了同一个 bug——服务器并没有正确感知文件的变化。为了解决这样的问题,Etag 作为 Last-Modified 的补充出现了。

Etag 是由服务器为每个资源生成的唯一的标识字符串,这个标识字符串是基于文件内容编码的,只要文件内容不同,它们对应的 Etag 就是不同的,反之亦然。因此 Etag 能够精准地感知文件的变化。

Etag 和 Last-Modified 类似,当首次请求时,我们会在响应头里获取到一个最初的标识符字符串,举个,它可以是这样的:

ETag: W / '2a3b-1602480f459';

那么下一次请求时,请求头里就会带上一个值相同的、名为 if-None-Match 的字符串供服务端比对了:

If-None-Match: W/"2a3b-1602480f459"

Etag 的生成过程需要服务器额外付出开销,会影响服务端的性能,这是它的弊端。因此启用 Etag 需要我们审时度势。正如我们刚刚所提到的——Etag 并不能替代 Last-Modified,它只能作为 Last-Modified 的补充和强化存在。 Etag 在感知文件变化上比 Last-Modified 更加准确,优先级也更高。当 Etag 和 Last-Modified 同时存在时,以 Etag 为准。

HTTP 缓存流程图

首次请求:

再次请求:

浏览器的同源策略

参考资料

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant