http之前端系列,知其然,知其所以然

290 阅读43分钟

HTTP 是浏览器中最重要且使用最多的协议,是浏览器和服务器之间的通信语言

HTTP是基于TCP/IP的关于数据如何在客户端和服务器端之间如何通信的协议。

请求头和响应头用于浏览器和服务器之间通信提供重要的信息

1 网络OSI七层模型

(Open Systems Interconnection Model 开放式通信系统互联参考模型)

前端一般优化有一条原则是,尽量少发请求,那么为何少发请求就能优化加载,它的开销到底是什么?

有必要分层吗?

然而这个问题也很好理解,分层后,如若某层产生变化,也不会波及整个系统。我们也知道,每一层其实都有不同的协议,如果各层混合在一起,切换协议就无从谈起,每次更换某一层,都需要改造整个系统。把各层之间的接口规划好之后,每个层次的内部设计就能够自由改动了。

  • 应用层:

针对特定应用的协议,它的作用是为应用程序提供服务并规定应用程序中通讯相关的细节,为应用提供服务,包括 Web 应用,常见的协议有 HTTP(超文本传输协议),DNS(域名系统),FTP(文件传输协议),TELNET、SMTP 等。

我们日常开发中,接触的协议主要以 HTTP 为主,那么把浏览器看作一个应用,当用户发起请求时,通过 HTTP协议获得数据以供浏览器使用,这就是应用层的用途。而请求时发生错误,对错误进行处理,也是应用层需要负责的。

  • 表示层:

表示层的作用是将应用处理的信息转换为适合网络传输的格式,或者将来自下一层的数据转换为上层能处理的格式。接收不同表现形式的信息,如文字流,图像,声音等,它主要负责数据格式的转换。具体来说,就是讲设备固有的数据格式转换为网络标准格式。常见的协议有 ASCII、SSL/TLS 等。

浏览器请求回一堆数据,是解析成文本还是图片,就由表示层决定。数据的压缩、加密、打包等功能也都在这层完成。

  • 会话层:

控制应用程序之间会话能力;何时建立连接,何时断开连接,以及保持连接多久,会话层作用是负责建立和断开通信连接(数据流动的逻辑通路),以及数据的分割等数据传输相关的管理。常见的协议有 ADSP、RPC 等。

  • 传输层:

传输层起着可靠传输的作用。是否有数据丢失?只在通信双方节点进行处理,而不需在路由器上处理。此层有两个具有代表性的协议: TCP 与 UDP。

  • 网络层:

网络层负责将数据传输到目标地址。经过哪个路由到达目标地址?目标地址可以使多个网络通过路由器连接而成的某一个地址。因此这一层主要负责寻址和路由选择。主要由 IP、ICMP 两个协议组成。

  • 数据链路层:

该层负责物理层面上互连的节点之间的通信传输。数据帧与比特流之间的转换,例如与1个以太网相连的两个节点间的通讯。常见的协议有 HDLC、PPP、SLIP 等。

数据链路层会将0、1序列划分为具有意义的数据帧传送给对端(数据帧的生成与接收)

  • 物理层:

物理层负责0、1比特流(0、1序列)与电压高低、光的闪灭之间的互换。比特流与电子信号之间的转换,典型的协议有 RS 232C、RS 449/422/423、V.24 和 X.21、X.21bis 等。

看着高大上,其实是将数据的0、1转换成电信号或者光信号。通过光纤、双绞线甚至是无限电波等介质传输到指定的地址。而传输过程中的集线器、中继器、调制解调器等,也属于物理层的传输介质。

数据就是这样在计算机和网络中进行传递的。这其中做的工作就是每层进行层层解包和附加自己所要传递的信息,术语叫做报头。

在四层,既传输层数据被称作段(Segments);三层网络层数据被称做包(Packages);二层数据链路层时数据被称为帧(Frames);一层物理层时数据被称为比特流(Bits)。

2 TCP/IP四层模型

网络七层模型是一个标准,而非实现。

网络四层模型是一个实现的应用模型。 网络四层模型由七层模型简化合并而来。

image.png

  • 应用层:DNS、FTP、HTTP、SMTP、TELNET、IRC、WHOIS
  • 传输层:TCP、UDP
  • 网络层:ARP、RARP
  • 链路层:ICMP、IP 用于处理连接网络的硬件部分,包括操作系统,硬件的设备驱动,NIC(网络适配器,即网卡)等物理可见部分。

3 HTTP报文结构

简介

用于http协议交互的信息被称为http报文,请求端(客户端)的http报文叫请求报文,响应端(服务器端)的叫做响应报文。http报文本身是由数据构成的字符串文本。

http报文大致可分为报文首部和报文主体两块,两者有空行(CR+LF)划分。通常不一定要有报文主体。

HTTP 类似,也是header + body的结构,具体而言:
起始行 + 头部 + 空行 + 实体

由于 http 请求报文和响应报文是有一定区别,因此我们分开介绍。

起始行

对于请求报文来说叫请求行,起始行类似下面这样:

GET /home HTTP/1.1

也就是请求方法 + 请求uri + http版本。

对于响应报文来说叫状态行,起始行一般张这个样:

HTTP/1.1 200 OK

响应报文的起始行也叫做状态行。由http版本、响应状态码和原因短语三部分组成。

值得注意的是,在起始行中,每两个部分之间用空格隔开,最后一个部分后面应该接一个换行,严格遵循ABNF语法规范。

头部

请求头还是响应头,包含表示请求和响应的各种条件和属性的各类首部。包含通用首部字段,请求首部,响应首部,实体首部。

空行

很重要,用来区分开头部和实体。

问: 如果说在头部中间故意加一个空行会怎么样?

那么空行后的内容全部被视为实体。

实体

实体主题就是具体的数据了,也就是body部分。请求报文对应请求体(请求实体), 响应报文对应响应体(响应实体)。

3 如何理解URI

URI, 全称为(Uniform Resource Identifier), 也就是统一资源标识符,它的作用很简单,就是区分互联网上不同的资源。

但是,它并不是我们常说的网址, 网址指的是URL(统一资源定位符), 表示资源的地址,URL是URI一个子集,实际上URI包含了URN和URL两个部分,由于 URL 过于普及,就默认将 URL视为 URI 了。

URI 的结构

URI 真正最完整的结构是这样的。 image.png

可能你会有疑问,好像跟平时见到的不太一样啊!先别急,我们来一一拆解。

scheme 表示协议名,比如http, https, file等等。后面必须和://连在一起。
user:passwd@ 表示登录主机时的用户信息,不过很不安全,不推荐使用,也不常用。
host:port表示主机名和端口。
path表示请求路径,标记资源所在位置。
query表示查询参数,为key=val这种形式,多个键值对之间用&隔开。
fragment表示 URI 所定位的资源内的一个锚点,浏览器可以根据这个锚点跳转到对应的位置。

举个例子: https://www.baidu.com/s?wd=HTTP&rsv_spt=1 这个 URI 中,https即scheme部分,www.baidu.com为host:port部分(注意,http 和 https 的默认端口分别为80、443),/s为path部分,而wd=HTTP&rsv_spt=1就是query部分。

URI 编码

URI 只能使用ASCII, ASCII 之外的字符是不支持显示的,而且还有一部分符号是界定符,如果不加以处理就会导致解析出错。

因此,URI 引入了编码机制,将所有非 ASCII 码字符和界定符转为十六进制字节值,然后在前面加个%。 如,空格被转义成了%20,三元被转义成了%E4%B8%89%E5%85%83。

4 HTTP的请求方法

请求方法的作用可以指定请求的资源按照期望产生某种行为。 有哪些请求方法?

http/1.1规定了以下请求方法

  1. GET: 通常用来获取资源,请求指定的页面信息,并返回实体主体
  2. POST: 提交数据,即上传数据,传输实体主体,
  3. HEAD: get请求,只不过返回的响应中没有具体的内容,用于获取报文首部
  4. PUT: 传输文件,从客户端向服务器传送的数据取代指定的文档的内容。?
  5. DELETE: 删除文件,删除资源(几乎用不到)
  6. CONNECT: 要求用隧道协议连接代理,建立连接隧道,用于代理服务器
  7. OPTIONS: 询问支持的方法,列出可对资源实行的请求方法,用来跨域请求
  8. TRACE: 追踪请求-响应的传输路径

GET 与 POST 请求之间的区别

最直观的区别就是GET把参数包含在URL中,POST通过request body传递参数

  • 参数的角度,GET 一般放在 URL 中,因此不安全,POST 放在请求体中,更适合传输敏感信息。
  • URL有长度限制,会影响 Get 请求,但是这个长度限制是浏览器规定的,不是 RFC 规定的
  • 点击返回/刷新按钮,GET没有影响,POST数据会重新发送(浏览器将会提示“数据被重新提交”)
  • 缓存的角度,GET 请求会被浏览器主动缓存下来,留下历史记录,而 POST 默认不会。
  • 编码的角度,GET 只能进行 URL 编码,只能接收 ASCII 字符,而 POST 没有限制。
  • 幂等性的角度,GET是幂等的,而POST不是。(幂等表示执行相同的操作,结果也是相同的)
  • TCP的角度,GET 请求会把请求报文一次性发出去,而 POST 会分为两个 TCP 数据包,首先发 header 部分,如果服务器响应 100(continue), 然后发 body 部分。(火狐浏览器除外,它的 POST 请求只发一个 TCP 包)

扩展

(大多数)浏览器通常都会限制url长度在2K个字节,而(大多数)服务器最多处理64K大小的url。

5 HTTP状态码

状态码告知从服务器返回的请求结果,借助状态码,用户可以知道服务器端是正常处理了请求,还是出现了错误。 平时遇到比较常见的状态码有:200, 204, 301, 302, 304, 400, 401, 403, 404, 422, 500,502

状态码的类别

  • 1XX infomational (信息性状态码) 接收的请求正在处理
  • 2XX Success (成功状态码) 请求正常处理完毕
  • 3XX Redirection (重定向状态码) 资源位置发生变动,需要进行附加操作已完成请求
  • 4XX Client Error (客户端错误状态码) 服务器无法处理请求
  • 5XX Server Error (服务器端状态码) 服务器处理请求出错

1xx

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

2xx

  • 200 OK是见得最多的成功状态码。通常在响应体中放有数据。
  • 201 Created 请求成功服务器创建了资源
  • 202 Accepted 表示服务器已接受请求,但尚未处理
  • 204 No Content含义与 200 相同,但响应头后没有 body 数据。
  • 206 Partial Content顾名思义,表示部分内容,它的使用场景为 HTTP 分块下载和断点续传,当然也会带上相应的响应头字段Content-Range。

3xx

  • 301 Moved Permanently即永久重定向,对应着
  • 302 Found,即临时重定向。

比如你的网站从 HTTP 升级到了 HTTPS 了,以前的站点再也不用了,应当返回301,这个时候浏览器默认会做缓存优化,在第二次访问的时候自动访问重定向的那个地址。

而如果只是暂时不可用,那么直接返回302即可,和301不同的是,浏览器并不会做缓存优化。

  • 304 Not Modified: 资源未修改 当协商缓存命中时会返回这个状态码。详见浏览器缓存

4xx

  • 400 Bad Request: 开发者经常看到一头雾水,只是笼统地提示了一下错误,并不知道哪里出错了。
  • 401 未授权,请求要求身份验证。 对于需要登录的网页,服务器可能返回此响应。
  • 403 Forbidden: 这实际上并不是请求报文出错,而是服务器禁止访问,原因有很多,比如法律禁止、信息敏感。
  • 404 Not Found: 资源未找到,表示没在服务器上找到相应的资源。
  • 405 Method Not Allowed: 请求方法不被服务器端允许。
  • 406 Not Acceptable: 资源无法满足客户端的条件。
  • 408 Request Timeout: 服务器等待了太长时间。
  • 409 Conflict: 多个请求发生了冲突。
  • 413 Request Entity Too Large: 请求体的数据过大。
  • 414 Request-URI Too Long: 请求行里的 URI 太大。
  • 429 Too Many Request: 客户端发送的请求过多。
  • 431 Request Header Fields Too Large请求头的字段内容太大。

5xx

  • 500 Internal Server Error: 仅仅告诉你服务器出错了,出了啥错咱也不知道。
  • 501 Not Implemented: 表示客户端请求的功能还不支持。
  • 502 Bad Gateway: 服务器自身是正常的,但访问的时候出错了,啥错误咱也不知道。
  • 503 Service Unavailable: 表示服务器当前很忙,暂时无法响应服务

6 HTTP首部字段

首部分为请求首部和响应首部,并且部分首部两种通用,接下来我们就来学习一部分的常用首部。

通用首部

通用字段	作用
Cache-Control	控制缓存的行为
Connection	浏览器想要优先使用的连接类型,比如 keep-alive
Date	创建报文时间
Pragma	报文指令
Via	代理服务器相关信息
Transfer-Encoding	传输编码方式
Upgrade	要求客户端升级协议
Warning	在内容中可能存在错误

请求首部

请求首部	作用
Accept	能正确接收的媒体类型
Accept-Charset	能正确接收的字符集
Accept-Encoding	能正确接收的编码格式列表
Accept-Language	能正确接收的语言列表
Expect	期待服务端的指定行为
From	请求方邮箱地址
Host	服务器的域名
If-Match	两端资源标记比较
If-Modified-Since	本地资源未修改返回 304(比较时间)
If-None-Match	本地资源未修改返回 304(比较标记)
User-Agent	客户端信息
Max-Forwards	限制可被代理及网关转发的次数
Proxy-Authorization	向代理服务器发送验证信息
Range	请求某个内容的一部分
Referer	表示浏览器所访问的前一个页面
TE	传输编码方式

响应首部

响应首部	作用
Accept-Ranges	是否支持某些种类的范围
Age	资源在代理缓存中存在的时间
ETag	资源标识
Location	客户端重定向到某个 URL
Proxy-Authenticate	向代理服务器发送验证信息
Server	服务器名字
WWW-Authenticate	获取资源需要的验证信息

实体首部

实体首部	作用
Allow	资源的正确请求方式
Content-Encoding	内容的编码格式
Content-Language	内容使用的语言
Content-Length	request body 长度
Content-Location	返回数据的备用地址
Content-MD5	Base64加密格式的内容 MD5检验值
Content-Range	内容的位置范围
Content-Type	内容的媒体类型
Expires	内容的过期时间
Last_modified	内容的最后修改时间

HTTP Request Header 请求头

image.png

HTTP Responses Header 响应头

image.png

7 Accept 系列字段

请求头中设置的

媒体类型 accept

accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9

accept首部字段(请求头中)通知服务器,有用户代理能够处理的媒体类型及媒体类型的相对优先级(q=来额外表示权重值,用分号进行分隔),一次可以指定多种媒体类型

响应头中通过Content-Type设置的

HTTP 灵活的特性,它支持非常多的数据格式,那么这么多格式的数据一起到达客户端,客户端怎么知道它的格式呢 当然,最低效的方式是直接猜,有没有更好的方式呢?直接指定可以吗?

答案是肯定的。不过首先需要介绍一个标准——MIME(Multipurpose Internet Mail Extensions, 多用途互联网邮件扩展)。它首先用在电子邮件系统中,让邮件可以发任意类型的数据,这对于 HTTP 来说也是通用的。 因此,HTTP 从MIME type取了一部分来标记报文 body 部分的数据类型,这些类型体现在Content-Type这个字段,当然这是针对于发送端而言,接收端想要收到特定类型的数据,也可以用Accept字段。 具体而言,这两个字段的取值可以分为下面几类:

text: text/html, text/plain, text/css 等
image: image/gif, image/jpeg, image/png 等
audio/video: audio/mpeg, video/mp4 等
application: application/json, application/javascript, application/pdf, application/octet-stream

压缩方式 Accept-Encoding

Accept-Encoding首部字段通知服务器,有用户代理支持的内容编码及内容编码的相对优先级(q=来额外表示权重值,用分号进行分隔),一次可以指定多种内容编码

当然一般这些数据都是会进行编码压缩的,采取什么样的压缩方式就体现在了发送方的Content-Encoding字段上, 同样的,接收什么样的压缩方式体现在了接受方的Accept-Encoding字段上。这个字段的取值有下面几种:

  • gzip: 当今最流行的压缩格式
  • deflate: 另外一种著名的压缩格式
  • br: 一种专门为 HTTP 发明的压缩算法
// 发送端
Content-Encoding: gzip
// 接收端
Accept-Encoding: gzip

支持语言 Accept-Language

Accept-Language首部字段通知服务器,有用户代理能够处理的自然语言集(如中文,英文等)及自然语言集的相对优先级(q=来额外表示权重值,用分号进行分隔),一次可以指定多种自然语言集

对于发送方而言,还有一个Content-Language字段,在需要实现国际化的方案当中,可以用来指定支持的语言,在接受方对应的字段为Accept-Language。如:

// 发送端
Content-Language: zh-CN, zh, en
// 接收端
Accept-Language: zh-CN, zh, en`

字符集Accept-Charset

Accept-Charset首部字段通知服务器,用户代理支持的字符集(如中文,英文等)及字符集的相对优先级(q=来额外表示权重值,用分号进行分隔),一次可以指定多种字符集

最后是一个比较特殊的字段, 在接收端对应为Accept-Charset,指定可以接受的字符集,而在发送端并没有对应的Content-Charset, 而是直接放在了Content-Type中,以charset属性指定。如:

// 发送端
Content-Type: text/html; charset=utf-8
// 接收端
Accept-Charset: charset=utf-8

左边请求头,右边响应头中设置

image.png

8 对于定长和不定长的数据,HTTP 是怎么传输的

定长包体

content-length: 2968

对于定长包体而言,发送端在传输的时候一般会带上 Content-Length, 来指明包体的长度。

Content-Length设置的值大于包体的长度的时候页面无法显示

不定长包体

Transfer-Encoding: chunked

表示分块传输数据,设置这个字段后会自动产生两个效果:

  1. Content-Length 字段会被忽略
  2. 基于长连接持续推送动态内容

9 HTTP 如何处理大文件的传输

对于几百 M 甚至上 G 的大文件来说,如果要一口气全部传输过来显然是不现实的,会有大量的等待时间,严重影响用户体验。因此,HTTP 针对这一场景,采取了范围请求的解决方案,允许客户端仅仅请求一个资源的一部分。

如何支持

当然,前提是服务器要支持范围请求,要支持这个功能,就必须加上这样一个响应头:

Accept-Ranges: none 不支持范围请求 Accept-Ranges:bytes 支持范围请求 用来告知客户端这边是支持范围请求的。

Range 字段拆解

而对于客户端而言,它需要指定请求哪一部分,通过Range这个请求头字段确定,格式为bytes=x-y。接下来就来讨论一下这个 Range 的书写格式:

  • 0-499表示从开始到第 499 个字节。
  • 500- 表示从第 500 字节到文件终点。
  • -100表示文件的最后100个字节。

服务器收到请求之后,首先验证范围是否合法,如果越界了那么返回416错误码,否则读取相应片段,返回206状态码。

同时,服务器需要在响应头中添加Content-Range字段,这个字段的格式根据请求头中Range字段的不同而有所差异。

具体来说,请求单段数据和请求多段数据,请求头是不一样的。

举个例子:
// 单段数据
Range: bytes=0-9
// 多段数据
Range: bytes=0-9, 30-39

接下来我们就分别来讨论着两种情况

单段数据

对于单段数据的请求,返回的响应如下:

HTTP/1.1 206 Partial Content
Content-Length: 10
Accept-Ranges: bytes
Content-Range: bytes 0-9/100

i am xxxxx

值得注意的是Content-Range字段,0-9表示请求的返回,100表示资源的总大小,很好理解。

多段数据

接下来我们看看多段请求的情况。得到的响应会是下面这个形式:

HTTP/1.1 206 Partial Content
Content-Type: multipart/byteranges; boundary=00000010101
Content-Length: 189
Connection: keep-alive
Accept-Ranges: bytes


--00000010101
Content-Type: text/plain
Content-Range: bytes 0-9/96

i am xxxxx
--00000010101
Content-Type: text/plain
Content-Range: bytes 20-29/96

eex jspy e
--00000010101--

这个时候出现了一个非常关键的字段Content-Type: multipart/byteranges;boundary=00000010101,它代表了信息量是这样的:

请求一定是多段数据请求

响应体中的分隔符是 00000010101

因此,在响应体中各段数据之间会由这里指定的分隔符分开,而且在最后的分隔末尾添上--表示结束。 以上就是 http 针对大文件传输所采用的手段。

10 HTTP 中如何处理表单数据的提交

在 http 中,有两种主要的表单提交的方式,体现在两种不同的Content-Type取值:

  • application/x-www-form-urlencoded
  • multipart/form-data

由于表单提交一般是POST请求,很少考虑GET,因此这里我们将默认提交的数据放在请求体中。

application/x-www-form-urlencoded

对于application/x-www-form-urlencoded格式的表单内容,有以下特点:

  • 其中的数据会被编码成以&分隔的键值对
  • 字符以URL编码方式编码。

如:

// 转换过程: {a: 1, b: 2} -> a=1&b=2 -> 如下(最终形式)
"a%3D1%26b%3D2"

multipart/form-data

对于multipart/form-data而言:

  • 请求头中的Content-Type字段会包含boundary,且boundary的值有浏览器默认指定。例: Content-Type: multipart/form-data;boundary=----WebkitFormBoundaryRRJKeWfHPGrS4LKe
  • 数据会分为多个部分,每两个部分之间通过分隔符来分隔,每部分表述均有 HTTP 头部描述子包体,如Content-Type,在最后的分隔符会加上--表示结束。

相应的请求体是下面这样:

Content-Disposition: form-data;name="data1";
Content-Type: text/plain
data1
----WebkitFormBoundaryRRJKeWfHPGrS4LKe
Content-Disposition: form-data;name="data2";
Content-Type: text/plain
data2
----WebkitFormBoundaryRRJKeWfHPGrS4LKe--

小结

值得一提的是,multipart/form-data 格式最大的特点在于:每一个表单元素都是独立的资源表述。另外,你可能在写业务的过程中,并没有注意到其中还有boundary的存在,如果你打开抓包工具,确实可以看到不同的表单元素被拆分开了,之所以在平时感觉不到,是以为浏览器和 HTTP 给你封装了这一系列操作。

而且,在实际的场景中,对于图片等文件的上传,基本采用multipart/form-data而不用application/x-www-form-urlencoded,因为没有必要做 URL 编码,带来巨大耗时的同时也占用了更多的空间。

11 HTTP代理

我们知道在 HTTP 是基于请求-响应模型的协议,一般由客户端发请求,服务器来进行响应。

当然,也有特殊情况,就是代理服务器的情况。引入代理之后,作为代理的服务器相当于一个中间人的角色,对于客户端而言,表现为服务器进行响应;而对于源服务器,表现为客户端发起请求,具有双重身份

那代理服务器到底是用来做什么的呢?

功能

  1. 负载均衡。客户端的请求只会先到达代理服务器,后面到底有多少源服务器,IP 都是多少,客户端是不知道的。因此,这个代理服务器可以拿到这个请求之后,可以通过特定的算法分发给不同的源服务器,让各台源服务器的负载尽量平均。当然,这样的算法有很多,包括随机算法轮询一致性hashLRU(最近最少使用)等等,不过这些算法并不是本文的重点,大家有兴趣自己可以研究一下。
  2. 保障安全。利用心跳机制监控后台的服务器,一旦发现故障机就将其踢出集群。并且对于上下行的数据进行过滤,对非法 IP 限流,这些都是代理服务器的工作。
  3. 缓存代理。将内容缓存到代理服务器,使得客户端可以直接从代理服务器获得而不用到源服务器那里。下一节详细拆解。

相关头部字段

Via

代理服务器需要标明自己的身份,在 HTTP 传输中留下自己的痕迹,怎么办呢?

通过Via字段来记录。举个例子,现在中间有两台代理服务器,在客户端发送请求后会经历这样一个过程:

客户端 -> 代理1 -> 代理2 -> 源服务器
复制代码

在源服务器收到请求后,会在请求头拿到这个字段:

Via: proxy_server1, proxy_server2
复制代码

而源服务器响应时,最终在客户端会拿到这样的响应头:

Via: proxy_server2, proxy_server1
复制代码

可以看到,Via中代理的顺序即为在 HTTP 传输中报文传达的顺序。

X-Forwarded-For

字面意思就是为谁转发, 它记录的是请求方IP地址(注意,和Via区分开,X-Forwarded-For记录的是请求方这一个IP)。

X-Real-IP

是一种获取用户真实 IP 的字段,不管中间经过多少代理,这个字段始终记录最初的客户端的IP。

相应的,还有X-Forwarded-HostX-Forwarded-Proto,分别记录客户端(注意哦,不包括代理)的域名协议名

X-Forwarded-For产生的问题

前面可以看到,X-Forwarded-For这个字段记录的是请求方的 IP,这意味着每经过一个不同的代理,这个字段的名字都要变,从客户端代理1,这个字段是客户端的 IP,从代理1代理2,这个字段就变为了代理1的 IP。

但是这会产生两个问题:

  1. 意味着代理必须解析 HTTP 请求头,然后修改,比直接转发数据性能下降。
  2. 在 HTTPS 通信加密的过程中,原始报文是不允许修改的。

由此产生了代理协议,一般使用明文版本,只需要在 HTTP 请求行上面加上这样格式的文本即可:

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

这样就可以解决X-Forwarded-For带来的问题了。

12 HTTP缓存

参考# 知其然,知其所以然,前端系列之浏览器工作原理

13 HTTP特点及缺点

特点

  • 灵活可扩展,主要体现在两个方面。一个是语义上的自由,只规定了基本格式,比如空格分隔单词,换行分隔字段,其他的各个部分都没有严格的语法限制。另一个是传输形式的多样性,不仅仅可以传输文本,还能传输图片、视频等任意数据,非常方便。
  • 可靠传输。HTTP 基于 TCP/IP,因此把这一特性继承了下来。这属于 TCP 的特性,不具体介绍了。
  • 请求-应答。也就是一发一收、有来有回, 当然这个请求方和应答方不单单指客户端和服务器之间,如果某台服务器作为代理来连接后端的服务端,那么这台服务器也会扮演请求方的角色。
  • 无状态。这里的状态是指通信过程的上下文信息,而每次 http 请求都是独立、无关的,默认不需要保留状态信息。

缺点

  • 无状态(不验证通信方的身份,有可能遭遇伪装)

所谓的优点和缺点还是要分场景来看的,对于 HTTP 而言,最具争议的地方在于它的无状态。服务器端无法判断请求是哪个客户端发来的

在需要长连接的场景中,需要保存大量的上下文信息,以免传输大量重复的信息,那么这时候无状态就是 http 的缺点了。

但与此同时,另外一些应用仅仅只是为了获取一些数据,不需要保存连接上下文信息,无状态反而减少了网络开销,成为了 http 的优点。

  • 明文传输(不加密)

即协议里的报文(主要指的是头部)不使用二进制数据,而是文本形式。内容可能被窃取

这当然对于调试提供了便利,但同时也让 HTTP 的报文信息暴露给了外界,给攻击者也提供了便利。WIFI陷阱就是利用 HTTP 明文传输的缺点,诱导你连上热点,然后疯狂抓你所有的流量,从而拿到你的敏感信息。

无法证明报文的完整性,所以有可能已 遭篡改

  • 队头阻塞问题

当 http 开启长连接时,共用一个 TCP 连接,同一时刻只能处理一个请求,那么当前请求耗时过长的情况下,其它的请求只能处于阻塞状态,也就是著名的队头阻塞问题。接下来会有一小节讨论这个问题。

14 HTTP/1:HTTP性能优化

性能优化

HTTP/0.9 1991

只有一个请求行,服务器只是返回数据,返回的文件内容是以 ASCII 字符流来传输的,因为都是 HTML 格式的文件

HTTP/1.0 1994 支持多种文件类型的文件下载

状态码,cache机制,用户代理,文件压缩方式accept-encoding,文件编码accept-Charset,文件语言accept-language

HTTP/1.1 文件传输的速度要求越来越高

  1. 改进持久连接 connection 默认开启的
  2. 不成熟的 HTTP 管线化
  • 队头阻塞

从前面的小节可以知道,HTTP 传输是基于请求-应答的模式进行的,报文必须是一发一收,但值得注意的是,里面的任务被放在一个任务队列中串行执行,一旦队首的请求处理太慢,就会阻塞后面请求的处理。这就是著名的HTTP队头阻塞问题。

  • 并发连接

对于一个域名允许分配多个长连接,那么相当于增加了任务队列,不至于一个队伍的任务阻塞其它所有任务。在RFC2616规定过客户端最多并发 2 个连接,不过事实上在现在的浏览器标准中,这个上限要多很多,Chrome 中是 6 个

但其实,即使是提高了并发连接,还是不能满足人们对性能的需求。

  • 域名分片

一个域名不是可以并发 6 个长连接吗?那我就多分几个域名。

比如 content1.sanyuan.com 、content2.sanyuan.com。

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

  1. 提供虚拟主机的支持

一台物理主机上绑定多个虚拟主机,每个虚拟主机都有自己的单独的域名,这些单独的域名都公用同一个 IP 地址

  1. 对动态生成的内容提供了完美支持
HTTP/1.0 数据大小Content-Length
HTTP/1.1 通过引入 Chunk transfer 机制

5. 客户端 Cookie、安全机制

使用cookie管理用户状态

HTTP/1.1 为网络效率做了大量的优化,最核心的有如下三种方式:

  • 增加了持久连接;
  • 浏览器为每个域名最多同时维护 6 个 TCP 持久连接;
  • 使用 CDN 的实现域名分片机制。

HTTP/1.1 的主要问题

对带宽的利用率却并不理想

带宽是指每秒最大能发送或者接收的字节数。我们把每秒能发送的最大字节数称为上行带宽,每秒能够接收的最大字节数称为下行带宽

  • 第一个原因,TCP 的慢启动。

  • 第二个原因,同时开启了多条 TCP 连接,那么这些连接会竞争固定的带宽。关键资源,普通资源

  • 第三个原因,HTTP/1.1 队头阻塞的问题。(应用层面的阻塞)

    我们知道在 HTTP/1.1 中使用持久连接时,虽然能公用一个 TCP 管道,但是在一个管道中同一时刻只能处理一个请求,在当前的请求没有结束之前,其他的请求只能处于阻塞状态。

15 HTTP/2:如何提升网络速度

2015 年 5 月正式发布 提升网络速度

Https 改进安全,http2改进性能主要体现在头部压缩,和多路复用两点

HTTP/2 的

头部压缩

在 HTTP/1.1 及之前的时代,请求体一般会有响应的压缩编码过程,通过Content-Encoding头部字段来指定

HTTP/2 对请求头和响应头进行了压缩,采用了对应的压缩算法——HPACK

HPACK 算法是专门为 HTTP/2 服务的,它主要的亮点有两个:

  • 首先是在服务器和客户端之间建立哈希表,将用到的字段存放在这张表中,那么在传输的时候对于之前出现过的值,只需要把索引(比如0,1,2,...)传给对方即可,对方拿到索引查表就行了。这种传索引的方式,可以说让请求头字段得到极大程度的精简和复用。

  • 其次是对于整数和字符串进行哈夫曼编码,哈夫曼编码的原理就是先将所有出现的字符建立一张索引表,然后让出现次数多的字符对应的索引尽可能短,传输的时候也是传输这样的索引序列,可以达到非常高的压缩率。

多路复用

一个域名只使用一个 TCP 长连接和消除队头阻塞问题

基于此,HTTP/2 的思路就是一个域名只使用一个 TCP 长连接来传输数据,这样整个页面资源的下载过程只需要一次慢启动,同时也避免了多个 TCP 连接竞争带宽所带来的问题。

另外,就是队头阻塞的问题,等待请求完成后才能去请求下一个资源,这种方式无疑是最慢的,所以 HTTP/2 需要实现资源的并行请求,也就是任何时候都可以将请求发送给服务器,而并不需要等待其他请求的完成,然后服务器也可以随时返回处理好的请求资源给浏览器。

HTTP/2 使用了多路复用技术,可以将请求分成一帧一帧的数据去传输,这样带来了一个额外的好处,就是当收到一个优先级高的请求时,比如接收到 JavaScript 或者 CSS 关键资源的请求,服务器可以暂停之前的请求来优先处理关键资源的请求。

多路复用的实现

通过引入二进制分帧层,就实现了 HTTP 的多路复用技术

image.png 从图中可以看出,HTTP/2 添加了一个二进制分帧层,那我们就结合图来分析下 HTTP/2 的请求和接收过程。

  1. 首先,浏览器准备好请求数据,包括了请求行、请求头等信息,如果是 POST 方法,那么还要有请求体。
  2. 这些数据经过二进制分帧层处理之后,会被转换为一个个带有请求 ID 编号的帧,通过协议栈将这些帧发送给服务器。
  3. 服务器接收到所有帧之后,会将所有相同 ID 的帧合并为一条完整的请求信息。
  4. 然后服务器处理该条请求,并将处理的响应行、响应头和响应体分别发送至二进制分帧层。
  5. 同样,二进制分帧层会将这些响应数据转换为一个个带有请求 ID 编号的帧,经过协议栈发送给浏览器。
  6. 浏览器接收到响应帧之后,会根据 ID 编号将帧的数据提交给对应的请求。

HTTP 队头阻塞

我们之前讨论了 HTTP 队头阻塞的问题,其根本原因在于HTTP 基于请求-响应的模型,在同一个 TCP 长连接中,前面的请求没有得到响应,后面的请求就会被阻塞。

后面我们又讨论到用并发连接和域名分片的方式来解决这个问题,但这并没有真正从 HTTP 本身的层面解决问题,只是增加了 TCP 连接,分摊风险而已。而且这么做也有弊端,多条 TCP 连接会竞争有限的带宽,让真正优先级高的请求不能优先处理。

而 HTTP/2 便从 HTTP 协议本身解决了队头阻塞问题。注意,这里并不是指的TCP队头阻塞,而是HTTP队头阻塞,两者并不是一回事。TCP 的队头阻塞是在数据包层面,单位是数据包,前一个报文没有收到便不会将后面收到的报文上传给 HTTP,而HTTP 的队头阻塞是在 HTTP 请求-响应层面,前一个请求没处理完,后面的请求就要阻塞住。两者所在的层次不一样。

那么 HTTP/2 如何来解决所谓的队头阻塞呢?

二进制分帧

首先,HTTP/2 认为明文传输对机器而言太麻烦了,不方便计算机的解析,因为对于文本而言会有多义性的字符,比如回车换行到底是内容还是分隔符,在内部需要用到状态机去识别,效率比较低。于是 HTTP/2 干脆把报文全部换成二进制格式,全部传输01串,方便了机器的解析。

原来Headers + Body的报文格式如今被拆分成了一个个二进制的帧,用Headers帧存放头部字段,Data帧存放请求体数据。分帧之后,服务器看到的不再是一个个完整的 HTTP 请求报文,而是一堆乱序的二进制帧。这些二进制帧不存在先后关系,因此也就不会排队等待,也就没有了 HTTP 的队头阻塞问题。

通信双方都可以给对方发送二进制帧,这种二进制帧的双向传输的序列,也叫做流(Stream)。HTTP/2 用流来在一个 TCP 连接上来进行多个数据帧的通信,这就是多路复用的概念。

可能你会有一个疑问,既然是乱序首发,那最后如何来处理这些乱序的数据帧呢?

首先要声明的是,所谓的乱序,指的是不同 ID 的 Stream 是乱序的,但同一个 Stream ID 的帧一定是按顺序传输的。二进制帧到达后对方会将 Stream ID 相同的二进制帧组装成完整的请求报文和响应报文。当然,在二进制帧当中还有其他的一些字段,实现了优先级和流量控制等功能,我们放到下一节再来介绍。

可以设置请求的优先级

我们知道浏览器中有些数据是非常重要的,但是在发送请求时,重要的请求可能会晚于那些不怎么重要的请求,如果服务器按照请求的顺序来回复数据,那么这个重要的数据就有可能推迟很久才能送达浏览器,这对于用户体验来说是非常不友好的。

为了解决这个问题,HTTP/2 提供了请求优先级,可以在发送请求时,标上该请求的优先级,这样服务器接收到请求之后,会优先处理优先级高的请求。

服务器推送

HTTP/2 还可以直接将数据提前推送到浏览器

当用户请求一个 HTML 页面之后,服务器知道该 HTML 页面会引用几个重要的 JavaScript 文件和 CSS 文件,那么在接收到 HTML 请求之后,附带将要使用的 CSS 文件和 JavaScript 文件一并发送给浏览器,这样当浏览器解析完 HTML 文件之后,就能直接拿到需要的 CSS 文件和 JavaScript 文件,这对首次打开页面的速度起到了至关重要的作用。

总结

HTTP/2 的一个核心特性是使用了多路复用技术,因此它可以通过一个 TCP 连接来发送多个 URL 请求。多路复用技术能充分利用带宽,最大限度规避了 TCP 的慢启动所带来的问题,同时还实现了头部压缩、服务器推送等功能,使得页面资源的传输速度得到了大幅提升。在 HTTP/1.1 时代,为了提升并行下载效率,浏览器为每个域名维护了 6 个 TCP 连接;而采用 HTTP/2 之后,浏览器只需要为每个域名维护 1 个 TCP 持久连接,同时还解决了 HTTP/1.1 队头阻塞的问题。

当然,HTTP/2 新增那么多的特性,是不是 HTTP 的语法要重新学呢?不需要,HTTP/2 完全兼容之前 HTTP 的语法和语义,如请求头、URI、状态码、头部字段都没有改变,完全不用担心。同时,在安全方面,HTTP 也支持 TLS,并且现在主流的浏览器都公开只支持加密的 HTTP/2, 因此你现在能看到的 HTTP/2 也基本上都是跑在 TLS 上面的了。最后放一张分层图给大家参考:

image.png

HTTP/2主要问题

  • TCP 的队头阻塞(数据传输层面上的阻塞)

在 TCP 传输过程中,由于单个数据包的丢失而造成的阻塞称为 TCP 上的队头阻塞。

  • TCP 建立连接的延时

RTT(Round-Trip Time):往返时延。 我们把从浏览器发送一个数据包到服务器,再从服务器返回数据包到浏览器的整个往返时间称为RTT

  • TCP 协议僵化

中间设备的僵化 操作系统也是导致 TCP 协议僵化的另外一个原因

16 HTTP/3:如何甩掉TCP、TLS的包袱,构建高效网络

QUIC 协议

HTTP/3 选择了一个折衷的方法——UDP 协议,基于 UDP 实现了类似于 TCP 的多路数据流、传输可靠性等功能,我们把这套功能称为 QUIC 协议

image.png

HTTP/3 中的 QUIC 协议集合了以下几点功能。

  • 实现了类似 TCP 的流量控制、传输可靠性的功能。虽然 UDP 不提供可靠性的传输,但 QUIC 在 UDP 的基础之上增加了一层来保证数据可靠性传输。它提供了数据包重传、拥塞控制以及其他一些 TCP 中存在的特性。
  • 集成了 TLS 加密功能。目前 QUIC 使用的是 TLS1.3,相较于早期版本 TLS1.3 有更多的优点,其中最重要的一点是减少了握手所花费的 RTT 个数
  • 实现了 HTTP/2 中的多路复用功能。和 TCP 不同,QUIC 实现了在同一物理连接上可以有多个独立的逻辑数据流(如下图)。实现了数据流的单独传输,就解决了 TCP 中队头阻塞的问题
  • 实现了快速握手功能。由于 QUIC 是基于 UDP 的,所以 QUIC 可以实现使用 0-RTT 或者 1-RTT 来建立连接,这意味着 QUIC 可以用最快的速度来发送和接收数据,这样可以大大提升首次打开页面的速度。

HTTP/3 的挑战

  • 第一,从目前的情况来看,服务器和浏览器端都没有对 HTTP/3 提供比较完整的支持。Chrome 虽然在数年前就开始支持 Google 版本的 QUIC,但是这个版本的 QUIC 和官方的 QUIC 存在着非常大的差异。
  • 第二,部署 HTTP/3 也存在着非常大的问题。因为系统内核对 UDP 的优化远远没有达到 TCP 的优化程度,这也是阻碍 QUIC 的一个重要原因。
  • 第三,中间设备僵化的问题。这些设备对 UDP 的优化程度远远低于 TCP,据统计使用 QUIC 协议时,大约有 3%~7% 的丢包率。

17 HTTPS:让数据传输更安全

https是什么?

https是什么

HTTPS 是在 HTTP 和 TCP 之间建立了一个安全层(SSL/TLS),HTTP 与 TCP 通信的时候,必须先经过一个安全层,对数据包进行加密,然后将加密后的数据包传送给 TCP,相应的 TCP 必须经过安全层将数据包解密,才能传给上面的 HTTP。

安全层有两个主要的职责:对发起 HTTP 请求的数据进行加密操作和对接收到 HTTP 的内容进行解密操作。

image.png

HTTPS=HTTP+加密+认证+完成性保护

  • 对称加密

对称加密是指加密和解密都使用的是相同的密钥。

image.png

  • 非对称加密

非对称加密算法有 A、B 两把密钥,如果你用 A 密钥来加密,那么只能使用 B 密钥来解密;反过来,如果你要 B 密钥来加密,那么只能用 A 密钥来解密。

image.png

  • 对称加密和非对称加密搭配使用

传输数据阶段依然使用对称加密,但是对称加密的密钥我们采用非对称加密来传输。

image.png

  • 添加数字证书

极客时间要证明这个服务器就是极客时间的,也需要使用权威机构颁发的证书,这个权威机构称为 CA(Certificate Authority)(认证中心),颁发的证书就称为数字证书(Digital Certificate)。

数字证书有两个作用

一个是通过数字证书向浏览器证明服务器的身份,另一个是数字证书里面包含了服务器公钥 数字证书的申请和验证

握手具体流程

  1. 首先是tcp的三次握手建立连接
  2. 浏览器向服务器发送对称加密套件列表、非对称加密套件列表和随机数 client-random;
  3. 服务器保存随机数 client-random,选择对称加密和非对称加密的套件,然后生成随机数 service-random,向浏览器发送选择的加密套件、service-random 和数字证书(包含公钥);
  4. 浏览器验证证书的有效性,如果验证通过,保存公钥,并用 client_random+server_random生成随机数 pre-master,然后利用公钥对 pre-master 加密(premaster),并向服务器发送加密后的数据;
  5. 服务器收到premaster,拿出自己的私钥,解密出 pre-master 数据,根据约定的加密算法对client_random+server_random+premaster(解密)使用公钥生成master-secret,然后发送预定成功。
  6. client收到生成同样的master-secert,对称加密秘钥传输完毕
  7. 然后之后的传输使用这个 master-secert 作为秘钥来进行数据的加解密

到此为止,服务器和浏览器就有了共同的 client-random、service-random 和 pre-master,然后服务器和浏览器会使用这三组随机数生成对称密钥,因为服务器和浏览器使用同一套方法来生成密钥,所以最终生成的密钥也是相同的。有了对称加密的密钥之后,双方就可以使用对称加密的方式来传输数据了。

需要特别注意的一点,pre-master 是经过公钥加密之后传输的,所以黑客无法获取到 pre-master,这样黑客就无法生成密钥,也就保证了黑客无法破解传输过程中的数据了。

完整的HTTPS请求流程图解

image.png

参考资料

《浏览器工作原理与实践》 极客时间

HTTP灵魂之问,巩固你的 HTTP 知识体系
HTTP 的15个常见知识点复习
我知道的HTTP请求