http HTTP面试题 - HTTP2 面试题
引言
根据网络上的常见面试题进行收集,基本能应付大部分的场景,HTTP大部分是八股,所以直接开始背书即可。
关联文章
基础问题
为什么要修改 HTTP?
HTTP 1.X 自出现以来便统治整个互联网15年以上,但是它的历史包袱也渐渐变大,高效加载资源的需求日趋明显,解决队头阻塞、头部臃肿等问题也逐渐被摆上台面。
HTTP1.X 的版本遗留了两个比较严重的问题:
- 连接过多导致TCP堵塞的控制变得无效化,网络拥塞造成不必要的带宽占用。
- 浏览器因为堵塞占用本不属于它的资源,同时会出现大量“重复”请求的资源数据。
业界曾经出现了大量方案尝试解决这些问题,比如:
- spriting 图片合并
- data: inlining 内联数据
- Domain Sharding 域名分片
- Concatenation 文件合并
然而无论如何优化,HTTP1.X 协议本身造成的网络拥塞是无法避免的,作为极客公司的Google为了推进自己的产品和业务需要更加高速的WEB互联网环境,推进HTTP的改革势在必行,Google本身也有足够的用户量和技术实力推进。
推进HTTP/2
IETF的HTTP工作组是HTTP/2的实际推动者,这个工作组维护了HTTP协议,而组织的成员由HTTP实现者、用户、网络运营商以及HTTP专家等组成。
除开IETF这个非盈利的神奇组织之外,还有各大主流浏览器的一些专家比如Firefox,Chrome,Twitter,Microsoft 的 HTTP stack,Curl 和 Akamai 等“大型”项目的工程师,以及诸如 Python、Ruby 和 NodeJS 之类的 HTTP 实现者。
注意HTTP协议是通过“邮件”沟通讨论进行完善和制定的,所有的讨论虽然构建在W3C的邮件服务商上,但是W3C本身对于HTTP推进没多大的帮助。
我们可以这么理解,IETF 是协议的真正推进者,也是协议标准的发布者,但是具体的协议制定可能来自各种团队和组织或者可以是个人,协议制定的日常工作是在邮件进行细节讨论,当然邮件讨论不能是聊闲话,每次邮件讨论只有存在产出的才算是合格,于是HTTP2协议就这样一步步完善并且最终完成。
服务器怎么样知道客户端需要 HTTP2 连接?
HTTP2和HTTP的请求协议都是http开头,普通用户一般是不知道客户端是否支持HTTP的(或者连HTTP是啥都不知道),那么客户端是如何在地址都是以Http开头的情况下识别请求是一个HTTP2的连接的呢?
这个知识点考查的是 连接前言,这个前言是 设计如此,无需过多纠结。
“连接前言”是标准的 HTTP/1 请求报文,使用纯文本的 ASCII 码格式,请求方法是特别注册的一个关键字“PRI”,全文只有 24 个字节。
In HTTP/2, each endpoint is required to send a connection preface as
a final confirmation of the protocol in use and to establish the
initial settings for the HTTP/2 connection. The client and server
each send a different connection preface.
The client connection preface starts with a sequence of 24 octets,
which in hex notation is:
0x505249202a20485454502f322e300d0a0d0a534d0d0a0d0a
That is, the connection preface starts with the string "PRI *
HTTP/2.0\r\n\r\nSM\r\n\r\n"). This sequence MUST be followed by a
SETTINGS frame ([Section 6.5](https://datatracker.ietf.org/doc/html/rfc7540#section-6.5)), which MAY be empty. The client sends
the client connection preface immediately upon receipt of a 101
(Switching Protocols) response (indicating a successful upgrade) or
as the first application data octets of a TLS connection. If
starting an HTTP/2 connection with prior knowledge of server support
for the protocol, the client connection preface is sent upon
connection establishment.
上面一大段话其实都是围绕 PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n这一串作为核心,根据HTTP定义规则如果客户端发送了这一串字符,并且通过 SETTINGS 帧告知服务端自己期望HTTPS2 连接,服务端就知道客户端需要的是TLS的HTTP2连接。
在 Wireshark 里,HTTP/2 的“连接前言”被称为“Magic”,意思就是“不可知的魔法”。
为什么叫HTTP2不叫HTTP2.0?
一句话就是就是为了规范化和消除歧义。工作组为了防止HTTP 1,HTTP1.1 这样的容易误解的协议名称做的改进,从HTTP2开始,所有的升级不会出现小版本升级,只有存在巨大更新,才会出现大版本的改动。
为什么要加入头部压缩?
具体可以看Patrick McManus对于头部压缩的性能提升的讨论:# In Defense of Header Compresson
我们直接从数据可以看到压缩过后的消息比没有压缩的要快出好几倍。
Test 50ms 100ms 200ms 300ms
1 52 102 202 302
2 52 102 202 302
3 358 808 1401 2108
4 256 506 1006 1542
Test 1 is with the cookie and zlib compression
Test 2 is without the cookie but with zlib
Test 3 is with the cookie and no zlib
Test 3 is without cookie and no zlib
HTTP/2 与 SPDY的关系
SPDY是谷歌为了对付HTTP1.X 的性能和网络阻塞问题的试验“玩具”,它用上亿的用户量兜底一步步改进SPDY的协议,这项技术后来也受到了Mozilla和Nginx等实现者的关注,SPDY后来顺水推舟成为了HTTP2的重要改进点。
后续IETF工作组经过讨论最终采用了 SPDY/2 作为HTTP2的基础,在IETF制定HTTP2的过程中,SPDY/2的核心开发团队都有全程参与,在后续Goole看到SPDY已经被HTTP2完全容纳了,于是在2015年直接删除了SPDY2,全面面向使用HTTP2。
HTTP/2 和 HTTP/1.x 的主要区别是什么?
在高版本的 HTTP/2 主要区别如下:
- 是二进制的,而不是文本的。
- 多路复用,实现应用层无队头阻塞。
- 一个连接可以进行并行处理。
- 使用HPACK算法压缩头部来减少开销。
- 允许服务器主动将响应"推送"到客户端缓存中。
- 请求允许进行服务端推送,双向并发传输。
为什么选择 HPACK?
第一个原因,SPDY2 建议无论请求还是回传响应数据方都使用单独的GZIP算法进行头部压缩(发现效率提升非常明显)。但是从那时起,一个"重要"的攻击方式 CRIME 诞生了,这种方式可以攻击加密文件内部的所使用的压缩流。
同样的,TLS压缩中也存在CRIME攻击的手段,黑客利用CRIME可以对于压缩TLS加密报文进行探测,并且可以解密恢复窃取密钥等信息,同时可以利用JS截取对TLS加密的HTTP传输数据,获取其中的Cookie信息和令牌窃取用户信息成为。
HPACK就是在在 GZIP 的安全性失效额基础上出现的,SPDY设计了HPACK算法加强Header压缩的安全性。
顺带一提,TLS 1.3 禁止压缩加密报文传输并且直接废弃压缩加密传输。
HTTP2 必须加密么?
虽然RFC文档没有明确要求HTTP2需要TLS加密,但是要知道主流浏览器大多都不支持不加密的HTTP2,所以HTTP2是理论上的自由选择加密,实际上的“加密连接”。
HTTP/2 可以扩展新字段吗?
HTTP/2 虽然在语法上做了很多改变,但是基本的报文结构是没有变的,如果传输新的字段或者传输新的类型,那么HTTP的前后兼容就会十分麻烦,这也是HTTP2没有在结构上做根本性改变的原因。
为了让使用者可以从HTTP1过渡到HTTP2,HTTP做了许多隐藏操作,比如连接前言,遵照HTTP协议请求头。
现实情况是HTTP2出现之后至今这么多年,本应该是8、90% 的普及率,实际只有50%的网站使用,可以看到一个升级协议除非足够像是TLS1.2那样足够吸引人,否则推行起来并不是容易的事情。
此外积极推动HTTP2发展的缔造者谷歌本身在宣传上下的功夫并不是很多。
HTTP2的安全性如何?
HTTP2的本身安全性并不靠谱。具体细节可以看:https://www.rfc-editor.org/rfc/rfc8164,在这里面简单描述了一些基本的安全攻击隐患,比如常见的降级攻击,HTTP2会把对应的响应字段删除,再比如服务器控制中使用“Alt-Svc”标头字段描述整个源的策略,服务器不应该允许用户内容设置或修改此标头的值等等。
此外厂商推行HTTP2 “要求”和TLS绑定上线的的另一个原因是TLS在当时的很多加密套件和加密方法才能在漏洞,椭圆曲线函数逐渐流行并且日渐成为网络安全传输的必需品,个人看来HTTP2的TLS绑定侧面反映了椭圆函数曲线加密的推行需求。
怎么知道浏览器是否支持HTTP2?
下图只列举一些主流浏览器,可以查看下面这一个网站:https://caniuse.com/http2,HTTP 2 已经公布很多年了,所以近几年的主流浏览器基本都支持HTTP2。
HTTP/2 会取代 HTTP/1.x 吗?
答案是不会,至少从HTTP2公布了近8年之后依然只有50%的网站支持HTTP2,从这一份数据就可以看出HTTP2的普及率虽然不错但是远没有想象中可观,个人认为更多人在期待HTTP3的普及。
不会完全取代的根本原因是不同的代理服务器以及项目部署的方式不同,不能强制让所有的服务器升级,HTTP1.X 依然会有很长的运行时间。
HTTP2 还有哪些缺陷?
HTTPS3 改进的都是HTTP2的缺陷,主要的问题如下:
1、没有解决TCP队头阻塞问题,导致如果有丢包请求会等待重传,阻塞后面的数据,有可能不如HTTP1.1的多个TCP连接 TCP 以及 TCP+TLS 建立连接的延时。
2、多路复用导致服务器压力上升,没有限制同时请求数。请求的平均数量与通常情况下相同,但很多服务器业务往往会有许多请求的短暂爆发导致瞬时 QPS 暴增。
3、HTTP2的多路复用容易产生大批量的请求Timeout,由于连接时内存在多个并行的流,而网络带宽和服务器资源有限,每个流的资源会被稀释,也就是说表面上看上去是非常接近的时间实际发送可能超时。
能对比一下 HTTP/2 与 HTTP/1、HTTPS 的相同点和不同点吗?
相同点:
- 下层都是都是基于TCP协议,HTTP/2虽然没有规定必须加密,但是浏览器会进行要求加密HTTP2,所以我们看到的大部分HTTP2实现服务网站都是加密连接的。
- 基于请求-响应模型,schema还是http或https不会有http2。
不同点:h2使用二进制传输消息并且通过HPACK压缩请求头实现流多路复用,服务器推送等。
使用h2和h2c划分加密和非加密请求有什么区别?
h2使用二进制传输消息并且通过HPACK压缩请求头实现流多路复用,服务器推送等。h2c优点是性能,不需要TLS握手以及加解密。可以通过curl工具构造h2c请求;
应该怎样理解 HTTP/2 里的“流”?
h2的流我们可以看作是实际存在的,因为它是使用帧传输数据的,相同 StreamId 的帧组成了消息以及流;通过类比类似于我们把一个积木玩具按照一定的规则拆分为不同的零件,零件可以一起发送过来,组装人员只需要知道组装顺序即可还原。也可以可以使用HTTP1的Chunked 的思路理解。
动态表维护、流状态转换很复杂,你认为 HTTP/2 还是“无状态”的吗?
个人认为HTTP2是存在状态这个概念的。对上层应用来说,Headers头部压缩当中动态表维护、流状态转换这些操作对它不可见,应用的实现方也不需要为了实现HTTP2传输进行手动的状态维护。头部压缩角度来看可以认为是“无状态”的。
HTTP2引入的帧状态是帧的进一步体现,具体可以看下面的流状态流转图。
+--------+
send PP | | recv PP
,--------| idle |--------.
/ | | \
v +--------+ v
+----------+ | +----------+
| | | send H / | |
,------| reserved | | recv H | reserved |------.
| | (local) | | | (remote) | |
| +----------+ v +----------+ |
| | +--------+ | |
| | recv ES | | send ES | |
| send H | ,-------| open |-------. | recv H |
| | / | | \ | |
| v v +--------+ v v |
| +----------+ | +----------+ |
| | half | | | half | |
| | closed | | send R / | closed | |
| | (remote) | | recv R | (local) | |
| +----------+ | +----------+ |
| | | | |
| | send ES / | recv ES / | |
| | send R / v send R / | |
| | recv R +--------+ recv R | |
| send R / `----------->| |<-----------' send R / |
| recv R | closed | recv R |
`----------------------->| |<----------------------'
+--------+
send: endpoint sends this frame
recv: endpoint receives this frame
H: HEADERS frame (with implied CONTINUATIONs)
PP: PUSH_PROMISE frame (with implied CONTINUATIONs)
ES: END_STREAM flag
R: RST_STREAM frame
HTTP/2 的帧最大可以达到 16M,你觉得大帧好还是小帧好?
仁者见仁智者见智,认为大帧好的会觉得小帧需要很多额外的头信息有数据冗余。 而认为小帧比较好则觉得小帧符合大部分常见的业务,当然如果在某些特定场景里比如下载大文件可以适当加大。
HTTP2头字段有什么特殊规定?
这个问题个人有些感触,过去个人碰到过CONTENT-TYP
、Content-Type
、 content-type
这样的请求头部,因为HTTP1.X对于头字段写法很随意,HTTP2 为了避开这些问题所有设置所有的头字段必须小写。具体看下面的RFC文档描述:
Just as in HTTP/1.x, header field names are strings of ASCII
characters that are compared in a case-insensitive fashion. However,
header field names MUST be converted to lowercase prior to their
encoding in HTTP/2
就像在 HTTP/1.x 中一样,标头字段名称是 ASCII 字符串
以不区分大小写的方式比较的字符。 然而,
标头字段名称必须在其之前转换为小写
HTTP/2 中的编码
随着 http2 的发展,前端性能优化中的哪些传统方案可以被替代
- 雪碧图
- 资源文件合并
- 域名发散
- 资源内联
http2 中 Stream 与 Frame 是什么关系?
- Stream 为 Request/Response 报文的双向通道,一个完整资源的请求与相应是一个 stream,特殊的 stream 作为 Settings、Window_Update 等 Frame 发送的通道
- Frame 为 http2 通信的最小单位,有 Data、Headers 等,一个 Stream 包含多个 Frame,如一条 http 请求包含 Header、Data Frame 等
实现细节问题
Http2 中 Server Push 与 WebSocket 有什么区别?
- HTTP2 Server Push,一般用于服务器解析
index.html
同时推送JPG/JS/CSS
等资源,而避免了服务器发送多次请求。 - websocket,用于服务器与客户端手动编写代码去推送进行数据通信。
谈谈 HTTP/2 如何解决“队头阻塞”问题
先说一下结论:HTTP2 解决了应用层的的队头阻塞,但没有解决TCP队头阻塞问题,我们可以认为HTTP2的队头阻塞很像是把管道化的概念实现的更好。
首先是HTTP1.X的队头阻塞问题,HTTP1在浏览器中的同一域名的并发连接数有限,如果连接数超过上限,排在后面的连接就需要等待前面的资源加载完成,有时候出现的浏览器空白并且一直“转圈”也是如此。
各大服务网站的解决方式是使用资源分割的方式,配合多域名和主机进行多个IP避开浏览器单个域名的限制,同时结合CDN加速请求。但是这样做需要分片多个TCP请求,TCP的连接请求的资源消耗比较大。
前面内容我们知道了,HTTP 2 通过改写HTTP数据交互方式为二进制,使用二进制帧的结构实现了应用层的多路复用,所有的二进制帧可以组成流并行可以跑在一个TCP连接上面,每个Stream都有一个唯一的StreamId,通过每个帧上设置ID(流标识符)在双方向上完成组装来还原报文,接收方需要根据ID的顺序拼接出完整的报文。
应用层上的队头阻塞是解决了,为什么说没有解决TCP队头阻塞?
我们需要明确HTTP本身是不具备数据传输能力的,虽然HTTP2识别数据和响应数据的方式变了,但是运载数据的还是TCP协议,而TCP协议实际上根本不认识什么HTTP数据,也不知道什么流,它只负责保证数据的安全传输。
在一个可靠的网络中,并发传输和配合没什么问题,HTTP和TCP互相不认识对方也不打紧,但是问题就出在现代社会的网络环境通常是频繁切换的,网络不畅事情时有发生。
在不稳定的网络传输中很有可能出现TCP数据传输阻塞问题,假设A网站要给B用户一个CSS文件,HTTP知道要被拆分为三个独立资源的包,按照ID连起来拼成完整的数据。此时如果数据包1和3都传输过去了,但2在传输过程突然出现丢包,此时接收方组装的时候发现ID不连续,这时候是不能够把1后面的数据包3传出去的,TCP的处理方式是将数据包3保存在其接收缓冲区(receive buffer)中,直到它接收到数据包2的重传副本,然后重新拼出完整的文件再返回给上层应用,HTTP拼接然后才能给浏览器(这至少需要往返服务器一次)。
在HTTP1.X中如果出现上面TCP队头阻塞情况,可以通过直接丢弃原有的TCP开新的TCP连接解决问题,虽然开销很大但是至少可以确保传输在正常进行。
而HTTP2在这种情况下就开倒车了,因为HTTP2的理念是一个TCP连接,所以只能通过等待TCP连接重传来解决丢包的问题,这种情况下整个TCP连接都要阻塞,如果是大文件传输,这种体验会更加糟糕。
结论:
TCP 协议本身的缺陷加上HTTP2一个TCP连接设计,HTTP2的TCP层队头阻塞问题十分显著。HTTP1.X在解决TCP队头阻塞虽然笨,但是实际体验要比HTTP2好得多。
以上这就是TCP的队头阻塞问题。顺带提一句HTTP3 通过了QUIC协议替换掉TCP协议,彻底实现了无队头阻塞的HTTP连接。
简单讲解一下http2的多路复用
HTTP1.X不支持多路复用。同一个域名并发请求会因为浏览器限制在6-8左右,多余连接会全部阻塞,过去的解决办法是使用多域名和CDN以及缓存服务器加速HTTP1.X。
HTTP1.X的多路复用尝试是管道化,但是它是非常失败的尝试,HTTP2.0 将它变得完善和可用。
2.0版本的多路复用指多个请求可以同时在一个TCP连接上并发,主要借助二进制帧中的标识进行区分实现链路的复用;
流标识符号表示帧属于哪一个流的,上限为2的31次方,接收方需要根据流标识的ID组装还原报文,同一个Stream的消息必须是有序的。
总的来说HTTP2的多路复用就是:
- 同域名下所有通信都在单个连接上完成,消除了因多个 TCP 连接而带来的延时和内存消耗。
- 单个连接上可以并行交错的请求和响应,之间互不干扰(流标识符的存在)。
- 支持双向推送,但是多路复用会造成带宽压力加大。
是否可以在不实现 TLS 的情况下实现 HTTP/2?
可以,但是我不建议这么干。在 HTTP2中,“h2”表示加密的 HTTP/2,“h2c”表示明文的 HTTP/2,这个c表示"clear text"。
原因如下:
- 只支持h2c的客户端:需要生成一个针对 OPTIONS 的请求。
- 只支持h2c 的服务器:可以使用一个固定的 101 响应来接收一个包含升级(Upgrade)消息头字段的请求。在响应中可以对于不支持的版本进行明确的状态码拒绝(505状态码)。
- 不想要处理HTTP1.X的请求:立即用 REFUSED_STREAM 错误码拒绝 stream,鼓励客户端使用HTTP2 进行重试。
使用明文需要双方向的兜底操作,并且服务实现方通常只能把控服务端这一块,那么还不如直接强制用TLS而直接用HTTPS请求来的直接,非TLS请求的提示在浏览器的体验好很多。如果是希望用户尽可能使用HTTP2,则可以使用第三种方案。
简单说一下HTTP/2
答案:[[HTTP - HTTP2 知识点]]
详细内容这里不做过多展开,因为HTTP2实现天翻地覆,展开讲又是一篇长文,回答问题主要针对下面的知识点:
- 兼容HTTP1
- 应用层队头阻塞解决
- 并发传输
- 多路复用
- 二进制帧
- 服务器推送
- HPACK/头部压缩
- 请求优先级
关联:[[HTTP - HTTP2 知识点]]
HTTP/2 连接需要 TCP_NODELAY 么?
首先看一下TCP对于 TCP_NODELAY
的描述。
If set, disable the Nagle algorithm. This means that segments are always sent as soon as possible, even if there is only a small amount of data. When not set, data is buffered until there is a sufficient amount to send out, thereby avoiding the frequent sending of small packets, which results in poor utilization of the network. This option is overridden by TCP_CORK; however, setting this option forces an explicit flush of pending output, even if TCP_CORK is currently set.
这个参数实际上就是Nagle算法的开关,Nagle算法是在过去带宽和网络通信缓慢的情况下设计的优化手段。
Nagle算法是时代的产物,因为当时网络带宽有限,简单理解它做的事情就是把接受到的网络资源不会进行传输而是先放到缓冲区缓冲,等到到达一定的数量再一次性发出去,当然同时也会设置一个定时器,如果缓存区一直不满,到了定时的时间同样一并发出,这样可以提高用户的使用体验。
现代的网络环境远没有以前那么贫瘠和昂贵,目前TCP/IP协议栈的设置已经默认把TCP_NODELAY=1
(关闭),但是并不是说这个算法完全派不上用场,有时候因为单个流的大数据量下载依然有可能派上用场,
避免保持 HPACK 状态?
可以通过发送一个 SETTINGS 帧,将状态(SETTINGS_HEADER_TABLE_SIZE
)设置到 0,然后 RST 所有的流,直到一个带有 ACT 设置位的 SETTINGS
帧被接收。
为什么 HPACK 中有 EOS 符号?
HPACK用的是霍夫曼编码,为了防止黑客利用字符空隙进行攻击,同时出于CPU处理效率考虑,会通过填充字符串的方式对于字节进行对齐,所以任意字符都有可能会有0-7个位的填充操作。
HPACK 的设计允许按字节比较霍夫曼编码的字符串,并且填充的时候要求使用EOS符号,同时根据霍夫曼编码的定义字符串数据:字符串文字的编码数据。如果 H 为“0”,则编码数据是字符串文字的原始八位字节。如果H 为 '1',则编码数据是字符串文字的 Huffman 编码。
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| H | String Length (7+) |
+---+---------------------------+
| String Data (Length octets) |
+-------------------------------+
为了证实上面的论述,我们可以直接阅读 https://datatracker.ietf.org/doc/rfc7541/ 这部分内容,里面存在EOS和霍夫曼编码的一些细节讨论。
由于霍夫曼编码的数据并不总是以八位字节边界结束,在它之后插入一些填充,直到下一个八位字节边界。至防止此填充被误解为字符串的一部分文字,代码的最高有效位对应于使用 EOS(字符串结尾)符号。
解码时,编码数据末尾的不完整代码是被视为填充和丢弃。填充严格更长超过 7 位必须被视为解码错误。填充不是对应于 EOS 代码的最高有效位符号必须被视为解码错误。霍夫曼编码的字符串
包含 EOS 符号的文字必须被视为解码
错误。
通过上面的讨论以及论证,意思已经很明显了,简单理解就为了安全性和CPU效率考虑,霍夫曼编码会,HPACK又是基于霍夫曼编码进行头部压缩的,为了使规范统一要求EOS的符号进行填充EOS符号。
首部压缩的实现原理是什么?
主要是“两表一码”,动态表,静态表,哈夫曼编码。
静态表负责存储固定的1-61位索引的常见首部字段,动态表用于一些经常出现变动的请求头部或者自定义请求头部,动态表的索引从62开始。
部署问题
如果 HTTP/2 是加密的,我该如何调试?
简单的方法是 NSS keylogging 与 知名的Wireshark 插件(包含在最新开发版本中)结合使用。这个方法对 Firefox 和 Chrome 均以及常见主流浏览器可适用。
如何使用 HTTP/2 服务器推送
服务器推送允许服务器无需等待客户端连接就可以向服务器推送数据,某些时候可以改善用户的使用体验,比如大带宽延迟的产品,为了尽可能减少网络连接传输上花费的时间。
根据请求内容变化而变化请求资源是不明智的,通常会造成缓存失效,详细情况可以查看 RFC 7234 的第 4 节
为了确保资源能够被正确接收,最好的处理方式是使用内容协商机制,使用 accept-encoding 报头字段的内容协商受到缓存的广泛尊重,但是可能无法很好地支持其他头字段。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。