关于http-2:HTTP20的二进制是什么

这篇纯正满足本人的好奇心 我如同是一个在海边游玩的孩子,不断为拾到比通常更润滑的石子或更漂亮的 贝壳而欢欣鼓舞,而展示在我背后的是齐全未探明的真谛之海。牛顿 写本文的时候,想起高中物理课本的一句话: 我如同是一个在海边游玩的孩子,不断为拾到比通常更润滑的石子或更漂亮的贝壳而欢欣鼓舞,而展示在我背后的是齐全未探明的真谛之海。那个时候不懂这句话,忙于刷分,现在纯正是为了本人的好奇心而探索一些问题,脑海中又开始复现这句话。本文的问题来自于后面的一篇文章:《HTTP学习笔记(三) HTTP/2》, 这篇文章里咱们提到了HTTP/2的几个特点: is binary, instead of textual二进制代替了文本is fully multiplexed, instead of ordered and blocking多路复用can therefore use one connection for parallelism并行申请uses header compression to reduce overhead压缩申请头,缩小耗费allows servers to “push” responses proactively into client caches容许服务器被动推送响应进入客户端的缓存中其实对于1我是不了解的,毕竟在计算机的世界都是“二进制”嘛,过后我的想法是难道是跟JDK解决String一样的操作,在JDK8之前,String自身是借助于char来存储的: public final class String implements java.io.Serializable, Comparable<String>, CharSequence { /** The value is used for character storage. */ private final char value[];}到了JDK 8之后, JDK借助byte来存储字符串: public final class String implements java.io.Serializable, Comparable<String>, CharSequence, Constable, ConstantDesc { @Stable private final byte[] value;} 毕竟一个char占两个字节, 一个byte只占一个字节,因为我之前用程序连贯过充电桩,接管充电桩的报文,给的报文都是byte类型的,byte更小,像String就带了一些额定的信息,所以我猜测,是这个意义上的二进制,然而这只是猜测,我想过用抓包工具去验证我的猜测,然而发现抓包工具我用的并部署,再加上HTTP/2.0都是加密报文,抓包挺麻烦的,我也想过看HTTP Client的源码,然而这两个奏效都太慢了,最近偶尔翻看MongDB的文档,翻到了这方面的阐明,这个问题就有了答案。其实HTTP也对下面的二进制进行了解释: ...

June 23, 2023 · 2 min · jiezi

关于http-2:HTTP面试题-HTTP2-面试题

http HTTP面试题 - HTTP2 面试题 引言依据网络上的常见面试题进行收集,根本能应酬大部分的场景,HTTP大部分是八股,所以间接开始背书即可。 关联文章关联:HTTP - HTTP2 知识点 根底问题为什么要批改 HTTP?HTTP 1.X 自呈现以来便统治整个互联网15年以上,然而它的历史包袱也慢慢变大,高效加载资源的需要日趋显著,解决队头阻塞、头部臃肿等问题也逐步被摆上台面。 HTTP1.X 的版本遗留了两个比较严重的问题: 连贯过多导致TCP梗塞的管制变得有效化,网络拥塞造成不必要的带宽占用。浏览器因为梗塞占用本不属于它的资源,同时会呈现大量“反复”申请的资源数据。业界已经呈现了大量计划尝试解决这些问题,比方: spriting 图片合并data: inlining 内联数据Domain Sharding 域名分片Concatenation 文件合并然而无论如何优化,HTTP1.X 协定自身造成的网络拥塞是无奈防止的,作为极客公司的Google为了推动本人的产品和业务须要更加高速的WEB互联网环境,推动HTTP的改革势在必行,Google自身也有足够的用户量和技术实力推动。 推动HTTP/2IETF的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连贯。 ...

October 22, 2022 · 5 min · jiezi

关于http-2:HTTP学习笔记三-HTTP2

这里简略的介绍一下HTTP 2.0。由HTTP 1.1 走向 HTTP 2.0写这篇文章的时候我在听B站UP主翻唱的歌曲,而后我灵机一动打算看看B站当初用的是HTTP的哪个版本,于是我摁下了F12键。 这个h2和h3代表的是HTTP 2.0 和3.0? 这版本号刷的这么快的吗? 不应该是2.1==>2.5 ==>3.0这样吗?为了验证我的想法,我关上了火狐浏览器。 所以就很忽然,原本依照打算只介绍HTTP/2.0的,而后HTTP/3.0也只得退出到学习打算中,所以每次学习一个知识点的时候,总会碰到新的,生也有涯,知也无涯的感觉,在后面两篇文章了,咱们曾经大抵的介绍HTTP 0.9 、1.0、1.1。 2.0 与1.1的不同1.1 的新个性咱们这里来再度介绍一下HTTP 1.1带来的改良: 连贯能够复用,节俭了屡次关上 TCP 连贯加载网页文档资源的工夫。HTTP 1.1 之前的连贯模型使短连贯,也是HTTP/1.0是短连贯,每一个HTTP申请都由它本人独立的连贯实现;这意味着发动每一个HTTP申请之前都会有一次TCP握手。 TCP协定自身握手自身就是消耗工夫的,所以TCP能够放弃更多的热连贯来适应负载。 火狐开发者文档是如是吐槽这个模型的: 除非是要兼容一个十分古老的,不反对长连贯的零碎,没有一个令人信服的理由持续应用这个模型。为了缓解这些问题,长连贯的概念便被设计进去了,甚至在HTTP/1.1之前。或者这被称为一个keep-alive连贯。 一个长连贯会放弃一段时间,反复用于发送一系列申请,节俭了新建TCP连贯握手的工夫,还能够利用TCP性能加强能力。当然这个连贯也不会始终保留着: 连贯在闲暇一段时间后会被敞开(服务器能够应用Keep-Alive协定头来指定一个最小的连贯放弃工夫) 然而长连贯也不是白璧无瑕的;就算是在闲暇状态,它还是会耗费服务器资源,而且在重负载时,还有可能蒙受Dos attacks攻打。在这种场景下,能够应用非长连贯,既尽快敞开那些闲暇的连贯,也能对性能有所晋升。 减少管线化技术,容许在第一个应答被齐全发送之前就发送第二个申请,以升高通信提早。管线化的英文是pipelining,pipelining还有另一个意思是流水线。我看火狐开发者文档的时候,中文版将pipelining在介绍HTTP的演变为翻译为管线化,然而在介绍连贯治理的时候,由将其译为流水线。我刚看的时候还认为是两种技术,事实上是一个名词的翻译。 然而火狐的开发者文档评估管线化技术,比拟难用,古代浏览器默认不会启用此个性。这个个性被更好的算法所代替,也就是HTTP/2 反对响应分块。简略的说这个代表分块传输,通常状况下,HTTP应答音讯中发送的数据是整个发送的,Content-Length音讯头部示意数据的长度,客户端须要直到那里是应答音讯的完结,以及后续应答音讯的开始。一个比拟常见的利用是断点续传,实现H5页面的大视频播放,实现渐进式播放,不须要将整个文件加载到内存中。 值得注意的是此个性在HTTP/2.0中并不反对,HTTP/2.0提供了一种更加有效率的数据流形式。 引入额定的缓存管制机制。简略的说就是引入了Cache-Control, 用Cache-control辨别了缓存的类型,制订了缓存过期策略。引入内容协商机制,包含语言,编码,类型等,并容许客户端和服务器之间约定以最合适的内容进行替换。凭借Host头, 可能使不同域名配置在同一个ip地址的服务器上。 有的同学看到这里会问,家喻户晓,HTTP用的是TCP协定,发送HTTP申请的时候,IP地址和端口必然是已知的,那么HOST的意义在哪里呢?举一个理论的例子是,Tomcat中部署多个服务,事实上一个Tomcat能够对应多个服务,当初是Spring Boot时代,Tomcat曾经默认集成在外面,是一个利用对一个Tomcat。如果你开发过Servlet,还没到Spring Boot,那个时候是打war包的,war被放在webapps上面,咱们在Tomcat中能够配置Host头,Tomcat会依据不同的host头转发到对应的服务上。关上Tomcat的server.xml: ### 流水线(pipelining)的设计问题 下面咱们提到了HTTP 1.1引入了流水线,容许第一个响应被齐全返回前,客户端发送第二个申请,我认为这是正当的设计,然而为什么古代浏览器大多没启用此个性吗? I would always recommend going to the authoritative source when trying to understand the meaning and purpose of HTTP headers. 如果你在尝试了解HTTP头的含意和用头时,我总是倡议你去原始权威文档. ...

July 23, 2022 · 2 min · jiezi

关于http-2:记录-http2-四个难以理解的疑惑点

文章基调不是科普类文章,不是科普 http2 性能的文章记录 http2 中难以了解的点,系作者在学习 http2 时的困惑,曾经最终的了解是集体的了解,可能有不谨严的中央,欢送探讨 如何了解 TCP 分帧 与 http2 分帧 的区别假如「传输残缺的数据」是「运输一个订单货物」,每「订单中的一个货物」占满「一个货车厢」TCP 位于传输层,能够了解为运输的货车 TCP 中的每一帧都是有序的,按发车工夫标记趟次,第一趟次,第二趟次,第三趟次,第 N 趟次TCP 能够同时发若干辆车,假如 4 辆车,则一次发车就有,第一趟次,第二趟次,第三趟次,第四趟次每辆车有些提前达到,有些很慢才到,不肯定依照发车的程序达到目的地当其中一个趟次的车回来了,才发下一趟车次。趟次的作用,是为了货品送到目的地的时候能够从新按顺序排列,能够将每趟次的货了解为高达模型的整机(一车只运送一个整机),有程序,能力辨认从新拼装起来将一个订单中的货,别离通过不同的趟次运输,就是「TCP 二进制分帧」,将订单的货(残缺的数据)分成每一车(帧)进行运输HTTP 处于应用层,一个 http 申请及响应,能够了解为下订单(申请)购买一批货(响应)的过程,(留神是一批,有若干个货物)而这批货并没有货品名称(无奈对应这批货对应的是哪个订单,无奈将响应与申请关联起来) 这时 http 还没有分帧化,粒度是以订单为单位,一个订单就是一个 http将一个 TCP 链接了解为一个运输合同 http0.9,1.0【问题】每一次订单都签一次运输合同,很麻烦 签一次运输合同(三次握手)下订单(申请)生产货物(服务器计算结果)发动运输(TCP 传输内容)收货物(响应)结一次运输合同的账(四次挥手)签一次运输合同(三次握手)下订单(申请)生产货物(服务器计算结果)发动运输(TCP 传输内容)收货物(响应)结一次运输合同的账(四次挥手)http1.1 keep-alive【改良】运输合同改成月结,复用 TCP 链接【问题】工厂无奈同时生产多个订单的货物,须要上一个订单收货 签一次运输合同(三次握手)下订单(申请)生产货物(服务器计算结果)发动运输(传输内容)收货物(响应)下订单(申请)生产货物(服务器计算结果)发动运输(传输内容)收货物(响应)结一次运输合同的账(四次挥手)http1.1 pipeline【改良】能够同时发多个订单了,通过收货程序,来辨认货物对应的是那一次订单的内容【改良】工厂能够同时生产多个订单的货【问题】订单存在依赖关系,即便第二次订单的货物生产好了,也得等第一次订单的货物生产好并全副传输完,能力发货。否则会被当做第一个订单的货 签一次运输合同(三次握手)下订单(第一个申请)下订单(第二个申请)同时生产第一个批和第二批货物(服务器计算结果)按程序发第一个订单的运输(传输内容)按程序收货物(响应,对应第一个响应)按程序发第二个订单的运输(传输内容)收货物(响应,对应第二个响应)结一次运输合同的账(四次挥手)http2 【改良】将一个订单的货拆分成多个批次,为每个批次标识上是哪个订单的货【改良】因为能辨认一批次货品所属订单,最小粒度从一个订单的货,改为一批次的。本来按订单运输,当初改为按批次运输,这个最小颗粒度的变动就是 http2 分帧:将一个响应或申请拆分成多个分帧片段【改良】最小粒度变成了批次,生产完一批次的货品,就能够马上传输,不须要等整个订单的货全副生产完才传输 签一次运输合同(三次握手)下订单(第一个申请)下订单(第二个申请)同时生产第一个订单和第二订单的货物(服务器计算)每生产批次货物,就给这批次的货打上标签,标识是哪个订单的货(分帧)筹备好了一批次货物,就发运输,不须要管是哪个订单的(传输)收货,从新分拣是哪个订单的货(依据分帧标识对应是那一次申请的响应)每生产批次货物,就给这批次的货打上标签,标识是哪个订单的货(分帧)筹备好了一批次货物,就发运输,不须要管是哪个订单的(传输)收货,从新分拣是哪个订单的货(依据分帧标识对应是那一次申请的响应)直到所有货物都传输实现结一次运输合同的账(四次挥手)总结 能够看出,tcp 的分帧与 http2 的分帧是不同维度的辨别tcp 的分帧维度是一个趟次运输(一个趟次运输一个货物),http2 的分帧维度是 一批货物(可能是一个货物,也可能是若干个货物)http2 分帧的实质是将本来一个订单(一个申请或响应),拆分成多个批次(多个帧),放大数据颗粒度,减少灵活性参考资料 https://segmentfault.com/q/1010000005167289https://blog.csdn.net/u598975767/article/details/112788129https://blog.wangriyu.wang/2018/05-HTTP2.html 为什么要分帧实质上,只有给一个订单的货(响应)打上订单(申请)标识,就能够标识是哪个订单的,就能够解决先订单依赖的问题(后一个订单不须要等前一个订单传输完),为什么须要将订单拆散为批次呢? 车的数量(TCP 通道宽度,流量宽度)是无限的,拆散了并不是放慢运输过程拆成批次(分帧)是为了从本源反对数据的调配传输,如果一个订单的量很大,按订单发货,就得等整个订单的货生产实现能力运输。而拆成批次(分帧)就能够将粒度减低,生产一个批次就运输一个批次,不须要等整个订单的货生产实现。如何了解 http1.1 是文本协定,http2 是二进制协定stackoverflow 中对应的探讨,对应国内论坛文本协定,信息传输过程经验以下步骤 编写文本,因为规定比拟涣散,可能存在多余的前后空格,多余的换行等状况传输文本,传输的是 ASCII,即文字对应的编码读取文本,了解文本,辨认 ASCII 码组合起来的单词,再通过字符串匹配的形式匹配意义二进制协定 这里的二进制,次要体现在两个方面「二进制帧封装」及「头部压缩」「二进制帧封装」行将数据打散,外包一层二进制帧数据(用于标识以后帧的个性) 所以是这一分帧层进行了二进制封装,而不是 http 的内容二进制,所以更适合的称说应该是「减少了二进制分帧层」这里的二进制帧数据,是用二进制为颗粒度代表数据的含意,每个 0 和 1 代表非凡的含意而文本协定,是通过 ASCII 对应的单词来表白含意,即代表数据的颗粒度不一样了,本来是有若干个字母,当初是由若干个比特「头部压缩」 ...

March 3, 2022 · 1 min · jiezi

关于http-2:HTTPHTTP2

HTTP/1.1 vs. HTTP/2 协定HTTP/2 以多种形式在 HTTP/1.1 的根底上进行了改良,以实现更快的内容交付和改良的用户体验,包含: 二进制协定:与 HTTP/1.1应用的文本协定相比,二进制协定耗费更少的带宽,更无效地解析并且更不容易出错。 此外,它们能够更好地解决空格、大写和行尾等元素。多路复用:HTTP/2是多路复用的,即它能够通过单个 TCP 连贯并行发动多个申请。后果,蕴含多个元素的网页通过一个 TCP 连贯传送。这些性能解决了 HTTP/1.1 中的行首阻塞问题,其中行前的数据包会阻止其余数据包的传输。头部压缩:HTTP/2 应用头部压缩来缩小 TCP 慢启动机制带来的开销。服务器推送:HTTP/2 服务器将可能应用的资源推送到浏览器的缓存中,甚至在它们被申请之前。 这容许浏览器在没有额定申请周期的状况下显示内容。进步安全性:Web 浏览器仅通过加密连贯反对 HTTP/2,从而进步了用户和应用程序的安全性。

December 5, 2021 · 1 min · jiezi

关于http-2:Go发起HTTP20请求流程分析后篇标头压缩

来自公众号:新世界杂货铺浏览倡议这是HTTP2.0系列的最初一篇,笔者举荐浏览程序如下: Go中的HTTP申请之——HTTP1.1申请流程剖析Go发动HTTP2.0申请流程剖析(前篇)Go发动HTTP2.0申请流程剖析(中篇)——数据帧&流控制回顾在前篇(*http2ClientConn).roundTrip办法中提到了写入申请header,而在写入申请header之前须要先编码(源码见https://github.com/golang/go/...)。 在中篇(*http2ClientConn).readLoop办法中提到了ReadFrame()办法,该办法会读取数据帧,如果是http2FrameHeaders数据帧,会调用(*http2Framer).readMetaFrame对读取到的数据帧解码(源码见https://github.com/golang/go/...)。 因为标头压缩具备较高的独立性,所以笔者基于下面提到的编/解码局部的源码本人实现了一个能够独立运行的小例子。本篇将基于本人实现的例子进行标头压缩剖析(残缺例子见https://github.com/Isites/go-...)。 单刀直入HTTP2应用 HPACK 压缩格局压缩申请和响应标头元数据,这种格局采纳上面两种技术压缩: 通过动态哈夫曼代码对传输的标头字段进行编码,从而减小数据传输的大小。单个连贯中,client和server独特保护一个雷同的标头字段索引列表(笔者称为HPACK索引列表),此列表在之后的传输中用作编解码的参考。本篇不对哈夫曼编码做过多的论述,次要对双端独特保护的索引列表进行剖析。 HPACK 压缩上下文蕴含一个动态表和一个动静表:动态表在标准中定义,并提供了一个蕴含所有连贯都可能应用的罕用 HTTP 标头字段的列表;动静表最后为空,将依据在特定连贯内替换的值进行更新。 HPACK索引列表意识静/动静表须要先意识headerFieldTable构造体,动静表和动态表都是基于它实现的。 type headerFieldTable struct { // As in hpack, unique ids are 1-based. The unique id for ents[k] is k + evictCount + 1. ents []HeaderField evictCount uint64 // byName maps a HeaderField name to the unique id of the newest entry with the same name. byName map[string]uint64 // byNameValue maps a HeaderField name/value pair to the unique id of the newest byNameValue map[pairNameValue]uint64}上面将对上述的字段别离进行形容: ...

October 26, 2020 · 8 min · jiezi

关于http-2:query-params过大引发的failed-to-load-response

概述http2 web server 在query params过大时,服务端会返回错误码ENHANCE_YOUR_CALM。因为chrome浏览器在version: 86.0.4240.111中的调试窗口没有显示具体的错误码。定位起来没那么间接。 定位过程有反馈在大量勾选指标时, 执行指令失败. inspect如图: 无业务日志. 在业务容器抓包, 申请没到业务容器.查看access.log, 包体达到了tengne.在客户端抓包. 如图:能够看到, GOAWAY后, 服务端断开了链接. 具体谬误如下:查阅rfc7540 The endpoint detected that its peer isexhibiting a behavior that might be generating excessive load.既然是query param过大引发的服务器断开链接, 从nginx中的文档找到了可能的参数:http2_max_field_size改为16k后问题解决. 其它切换到 http1.1 有相似的谬误http 414

October 24, 2020 · 1 min · jiezi

关于http-2:HTTP2-协议常见疑问译

为什么订正 HTTP 协定HTTP/1.1 利用于 Web 已有15年的历史,协定的缺点和有余开始浮现。 比照过来,当初的 web 页面须要加载更多资源,HTTP1.x 协定规定一个 TCP connection 不能并行发动多个 request 申请,这使得页面在疾速加载大量资源时变得艰难。 为解决上述问题,HTTP1.1 协定容许浏览器应用多个 TCP connection 来并行发动多个 request 申请。这种解决形式存在缺点,应用太多的 connection 会事与愿违(TCP 拥塞管制会导致网络效率低下),同时也会呈现不偏心景象(浏览器都设法占用超出它本该调配的网络资源)。 HTTP/2 跟 SPDY 有什么关系在 SPDY 协定被 Mozilla 和 nginx 等厂商实现后,绝对于 HTTP/1.x 展现出了显著的性能晋升,这时 HTTP/2 协定的探讨和指定开始提上议程。 通过一轮提议和投票,最被抉择了 SPDY/2 作为 HTTP/2 协定的根底。尔后,在工作组和实现者的探讨下 HTTP/2 协定又做出了一系列变更。在这个过程中,SPDY 协定的外围开发者参加了 HTTP/2 协定的开发,这其中包含 Mike Belshe 和 Roberto Peon。 2015年9月,Google 发表为了反对 HTTP/2 协定,未来不再反对 SPDY 协定。 HTTP/2 跟 HTTP/1.x 的区别总体来说区别有以下几点: 应用二进制来代替文本格式应用多路复用来代替有序和阻塞应用一个 connection 来解决并行申请应用 header 压缩来减小 header 大小容许服务端应用 push 来事后推送客户端须要的 cache为什么 HTTP/2 是二进制的二进制协定解析效率更高,更节俭网络资源,绝对于 HTTP/1.1 协定应用文本格式的空格或空行来解析数据,二进制协定更不容易出错 ...

October 12, 2020 · 1 min · jiezi

HTTP2协议主要改进点

1、改成二进制协议,每次传输二进制帧,帧有以下几个字段 类型type,长度length,flag,StringID流标志,Payload负载,最基础的两种类型HEAD类型和DATA类型 2、多路复用,可以在一个连接上,同时传输多个数据流,每个流的传输顺序是固定的,按先后到达拼接 3、支持优先级,通过权重 4、支持重置中断,在HTTP/1.1中,如果一个请求发出去了,在没有发送完的情况下,是不好取消的,只能断开这次的TCP连接,但是断开重连有有点费时,HTTP2可以发送一个RST_STREAM帧,表示取消这次请求传输,后面重新开始传 5、头部压缩,大部分的请求头部都是相似的,尤其类似cookie这些信息,有时候占比很重,可以压缩 6、支持服务端推送,服务端在响应客服端请求A资源时,预估可能马上也会用到B资源,服务端可以把B资源发送给客户端,当然前提是客户端显示的告知服务端,客户端允许这种推送,并且最终客户端有决定权是否接受,如果不接受,可以发送一个RST_STREAM帧取消 7、支持流量控制,支持客户端和服务端沟通彼此的数据窗口大小 文章同步发布: https://www.geek-share.com/de...

October 2, 2019 · 1 min · jiezi

HTTP2更快的页面加载时间

作者:Alex Ronin翻译:疯狂的技术宅 原文:https://frontnet.eu/http-2-fa... 未经允许严禁转载 也许人们已经听说过 HTTP2,有很多数大公司都使用HTTP2,如Google、Youtube、Facebook ...... 那么什么是HTTP2?我们现在就知道了。 HTTP 的历史 HTTP 又称超文本传输协议,就是你的浏览器与你正在访问的网站的 Web 服务器通信的方式。 两台(或多台)计算机通过 Internet 相互通信的方式有很多种,HTTP 只是用于 Web 浏览的一种方式。 第一个官方 HTTP 版本(HTTP 1.0)于1996年作为RFC1945发布。 随着 Web 的快速发展,有了更多的 css,js 组件,这意味着我们需要更多资源,在某些情况下还需要同时下载多个资源。在使用时会发生什么 1 connection / 1 resourcesHTTP 1.0 的机制无法实现带宽优化。 在 1999 年发布的版本 HTTP/1.1 解决了流水线概念这个问题。然后 HTTP/1.1 版本继续更新并使用到现在为止。 虽然有所改进,但是流水线并没有完全解决 HTTP/1.0 的问题。虽然人们觉得“还不错!”,Google 的人们觉得“不行!”,所以他们发布了一个名为 SPDY 的新协议来改善页面加载时间。 。 SPDY 通过压缩,多路复用和优先级排序技术实现了减少页面加载时间的目标。 2012年7月,SPDY 开发团队公开宣布它正朝着标准化方向发展。 Chromium、Mozilla Firefox、Opera、Amazon Silk、Internet Explorer 和 Safari 浏览器也实现了 SPDY。 部署 SPDY 后,与 HTTP/1.x 相比,它显示出显著的改进,并引起了 Firefox 和 nginx 等开发人员的兴趣。不久之后开发人员开始讨论 HTTP/2。在调用过程和提案选择完成之后,SPDY/2 作为 HTTP/2 的基础。从那时起,根据工作组的讨论和实施者的反馈,发生了一些变化。截至2015年5月,HTTP/2 规范发布(RFC 7540)。 ...

August 19, 2019 · 1 min · jiezi

http系列HTTP20新特性二进制传输多路复用Haeder压缩服务端pushQUIC协议

一、前言HTTP 2.0 相比于 HTTP 1.X,可以说是大幅度提高了 web 的性能。 在 HTTP 1.X 中,为了性能考虑,我们会引入雪碧图、将小图内联、使用多个域名等等的方式。这一切都是因为浏览器限制了同一个域名下的请求数量,当页面中需要请求很多资源的时候,队头阻塞(Head of line blocking)会导致在达到最大请求数量时,剩余的资源需要等待其他资源请求完成后才能发起请求。 二、HTTP 2.0感受下 HTTP 2.0 比 HTTP 1.X 到底快了多少,地址:https://http2.akamai.com/demo 在 HTTP 1.X 中,因为队头阻塞的原因,你会发现请求是这样的 在 HTTP 2.0 中,因为引入了多路复用,你会发现请求是这样的 三、HTTP 2.0核心3.1 二进制传输HTTP 2.0中所有加强性能的核心带你在于此--二进制传输。 之前的HTTP的版本中,我们传输数据方式--文本传输。 在HTTP 2.0中引入了新的编码机制,所有传输的数据都会被分隔,并采用二级制格式编码。 3.2 多路复用在 HTTP 2.0 中,有两个非常重要的概念,分别是帧(frame)和流(stream)。 帧代表着最小的数据单位,每个帧会标识出该帧属于哪个流,流也就是多个帧组成的数据流。 多路复用,就是在一个 TCP 连接中可以存在多条流。换句话说,也就是可以发送多个请求,对端可以通过帧中的标识知道属于哪个请求。通过这个技术,可以避免 HTTP 旧版本中的队头阻塞问题,极大的提高传输性能。 3.3 Header压缩在 HTTP 1.X 中,我们使用文本的形式传输 header,在 header 携带 cookie 的情况下,可能每次都需要重复传输几百到几千的字节。 在 HTTP 2.0 中,使用了 HPACK 压缩格式对传输的 header 进行编码,减少了 header 的大小。并在两端维护了索引表,用于记录出现过的 header ,后面在传输过程中就可以传输已经记录过的 header 的键名,对端收到数据后就可以通过键名找到对应的值。 ...

June 28, 2019 · 1 min · jiezi

http10-http11-http20特性及区别

http1.0特性无状态:服务器不跟踪不记录请求过的状态无连接:浏览器每次请求都需要建立tcp连接无状态对于无状态的特性可以借助cookie/session机制来做身份认证和状态记录 无连接无连接导致的性能缺陷有两种: 1. 无法复用连接 每次发送请求,都需要进行一次tcp连接(即3次握手4次挥手),使得网络的利用率非常低 2. 队头阻塞 http1.0规定在前一个请求响应到达之后下一个请求才能发送,如果前一个阻塞,后面的请求也给阻塞的 http1.1特性为了解决http1.0的性能缺陷,http1.1出现了 http1.1特性: 长连接:新增Connection字段,可以设置keep-alive值保持连接不断开管道化:基于上面长连接的基础,管道化可以不等第一个请求响应继续发送后面的请求,但响应的顺序还是按照请求的顺序返回缓存处理:新增字段cache-control断点传输长连接http1.1默认保持长连接,数据传输完成保持tcp连接不断开,继续用这个通道传输数据 管道化基于长连接的基础,我们先看没有管道化请求响应: tcp没有断开,用的同一个通道 请求1 > 响应1 --> 请求2 > 响应2 --> 请求3 > 响应3管道化的请求响应: 请求1 --> 请求2 --> 请求3 > 响应1 --> 响应2 --> 响应3即使服务器先准备好响应2,也是按照请求顺序先返回响应1 虽然管道化,可以一次发送多个请求,但是响应仍是顺序返回,仍然无法解决队头阻塞的问题 缓存处理当浏览器请求资源时,先看是否有缓存的资源,如果有缓存,直接取,不会再发请求,如果没有缓存,则发送请求 通过设置字段cache-control来控制 断点传输在上传/下载资源时,如果资源过大,将其分割为多个部分,分别上传/下载,如果遇到网络故障,可以从已经上传/下载好的地方继续请求,不用从头开始,提高效率 在 Header 里两个参数实现的,客户端发请求时对应的是 Range 服务器端响应时对应的是 Content-Range http2.0特性二进制分帧多路复用: 在共享TCP链接的基础上同时发送请求和响应头部压缩服务器推送:服务器可以额外的向客户端推送资源,而无需客户端明确的请求二进制分帧将所有传输的信息分割为更小的消息和帧,并对它们采用二进制格式的编码 多路复用基于二进制分帧,在同一域名下所有访问都是从同一个tcp连接中走,http消息被分解为独立的帧,乱序发送,服务端根据标识符和首部将消息重新组装起来 区别http1.0 到http1.1的主要区别,就是从无连接到长连接http2.0对比1.X版本主要区别就是多路复用

June 19, 2019 · 1 min · jiezi

都9102年了确认不升级到HTTP2吗

HTTP/2协议在2015年就已经正式被发表了,但是现在有许多网站还是采用的http/1.1协议,都9102年了,确认不升级一下吗?想必该有人问了,为啥要升级,http/1.1用的好好的,http/2有啥优势? 首先来看一下http/1.1的缺点,也就是http/2的优势所在了。 HTTP/1.1 缺点对头阻塞(Head-of-line blocking)HTTP/1.1协议虽然可以在同一个TCP连接上发送多个请求,但是这多个请求是有顺序的,必须处理完第一个请求才会响应下一个请求。如果第一个请求处理的特别慢,后面的所有请求就需要排队。TCP 连接数限制对于同一个域名,浏览器最多只能同时创建 6 ~ 8 个TCP连接。如果一个页面有十个请求同时发送,那么只能等第一次的 6 ~ 8 个请求都返回了才能继续接下来的 2 ~ 4 个请求。这怎么能行?域名分片技术应运而生。就是把资源分配到不同的域名下(可以是二级子域名),这样就解决了限制,愉快~但是滥用域名分片技术也不行,因为每个TCP连接也是很费时的(这个大家都懂的)。Header 内容繁多,有时有可能会超过响应内容,并且每次有许多字段都是重复传输。HTTP/1.1是文本协议传输,不够安全。基于以上缺点,也出现了许多优化手段:雪碧图、合并脚本和样式表、资源内联、域名分片等优化工作,但是如果HTTP协议足够好的话,本可以避免这些额外的操作。 HTTP/2http2相比于http/1.1的新特性包括: 多路复用 (MultiPlexing),单一长连接,二进制格式传输,请求优先级设置头部header压缩服务端推送Server Push下面来一一介绍。 多路复用 (MultiPlexing)HTTP/2 复用 TCP 连接,在一个连接里,客户端和浏览器都可以同时发送多个请求或回应,这些请求或回应在逻辑上分成了很多流(stream),每个流中可以传输若干消息(Message),每个消息由若干最小的二进制帧(Frame)组成。而且不用按照顺序一一对应(但是同一个请求或响应的帧必须是有序的,不同的可以无序),这样就避免了"队头堵塞",减少了 TCP 连接数量和 TCP 连接慢启动造成的问题。http2还可以对stream指定优先级,优先级越高的越先响应。比如可以把js和css的优先级设置的高一些,让他们优先下载并执行。优先级也能动态的修改。 多路复用图: 头部header压缩HTTP是无状态的,每次请求都需要附带一些信息。但是许多字段都是重复的,会浪费带宽影响速度。HTTP/2对头部信息采用HPACK压缩算法来减少报文头的大小。具体做法是把报文头信息中常见的名和值对应一个索引,维护了一张静态字典,index从1到61,比如把:method:GET映射成2,这样就能达到压缩头部的作用。但是对于一些动态的资源,比如,user-agent,需要维护一份可动态添加内容的共同动态字典,这份动态字典在数据传输的过程中逐步建立,index从62开始。然后将映射之后的数据用huffman编码。 静态字典表: 服务端推送(Server Push)以前是客户端向服务器请求什么,服务器就发送什么,十分吝啬。现在有了服务端推送,客户端向服务端要了一滴水,服务端可以返回整个森林。这允许服务器直接提供浏览器渲染页面所需资源,而无须浏览器在收到、解析页面后再提起一轮请求,节约了加载时间。比如浏览器向服务器请求一个页面,之前需要等到浏览器收到页面解析html后,发现里面引用了静态资源,浏览器再向服务器发送静态资源的请求。但是现在服务器可以直接将页面和所需的静态资源一并返回。 nginx如何配置nginx开始HPPT/2非常简单,只需在HTTPS设置后加上http2即可。 server { listen 443 ssl http2;}讲了HTTP/2这么多的优点,客官确定不升级一下吗?升级之后就可以对雪碧图、合并脚本和样式表、资源内联、域名分片这些优化say goodbye了。以后谁再问你网站优化都有哪些方法,上面的几种就不用说了。

June 5, 2019 · 1 min · jiezi

TCP-Connection-Reuse-on-HTTP11-and-20

I saw the introduction of http/2.0 in developers.google.com. However, I feel confused about the request and response multiplexing. So I decided to make a demo or an example for better understanding. TCP Connection ReuseWhen I saw the TCP connection reuse, I had a lot of questions in my head. For instances, how do I know if the TCP was reused?What would the network be if the TCP wasn't reused?It seems that HTTP 1.1 also supports TCP connection reuse. So, what's the difference?....After searching, I found that there is a Connection ID column in the chrome dev tool Network panel. For example, here is the network image of baidu.com : ...

April 28, 2019 · 3 min · jiezi

《HTTP/2 基础教程》 读书笔记

最近粗线了不少 HTTP2 相关的帖子和讨论,感觉新一轮的潮流在形成,所以最近找了本 HTTP2 相关书籍做知识储备,刚好记成笔记以备后询 这本书本身不错,缺点就是翻译的有点蹩脚,另外因为是 2017 年出的书,所以有些内容时效性不太好,比如关于 Chrome 的部分,所以我根据 Chrome 的官方文档增加了点内容 ????1. HTTP进化史1.1 HTTP/0.9、HTTP/1.0、HTTP/1.1HTTP/0.9: 是个相当简单的协议。它只有一个方法(GET),没有首部,其设计目标也无非是获取 HTML(也就是说没有图片,只有文本)。HTTP/1.0: 多了很多功能,首部、错误码、重定向、条件请求等,但仍存在很多瑕疵,尤其是不能让多个请求共用一个连接、缺少强制的 Host 首部、缓存的选择也相当简陋,这三点影响了 Web 可扩展的方式。HTTP/1.1: 增加了缓存相关首部的扩展、OPTIONS 方法、Upgrade 首部、Range 请求、压缩和传输编码、管道化等功能。因为强制要求客户端提供 Host 首部,所以虚拟主机托管成为可能,也就是在一个 IP 上提供多个 Web 服务。另外使用了 keep-alive 之后,Web 服务器也不需要在每个响应之后关闭连接。这对于提升性能和效率而言意义重大,因为浏览器再也不用为每个请求重新发起 TCP 连接了。1.2 HTTP/2HTTP2 被希望达到以下特性:相比 HTTP/1.1,性能显著提高;解决 HTTP 中的队头阻塞问题;并行的实现机制不依赖与服务器建立多个连接,从而提升 TCP 连接的利用率,特别是在拥塞控制方面;保留 HTTP/1.1 的语义,可以利用已有的文档资源,包括(但不限于) HTTP 方法、状态码、URI 和首部字段;明确定义 HTTP/2.0 和 HTTP/1.x 交互的方法,特别是通过中介时的方法(双向);明确指出它们可以被合理使用的新的扩展点和策略。2. HTTP/2 快速入门2.1 启动并运行很多网站已经在用HTTP/2(h2)了,比如 Facebook、Instagram、Twitter 等,下面介绍以下如何自己搭建 h2 服务器。要运行 h2 服务器,主要分两步:获取并安装一个支持 h2 的 Web 服务器下载并安装一张 TLS 证书,让浏览器和服务器通过 h2 连接2.2 获取证书证书可以通过三种方式获取:使用在线资源自己创建一张证书从数字证书认证机构(CA)申请一张证书前两个方法 将创建自签名证书,仅用于测试,由于不是 CA 签发的,浏览器会报警后面关于创建 h2 服务器的步骤就不记了,可以百度下3. Web优化『黑魔法』的动机与方式3.1 当前的性能挑战3.1.1 剖析Web页面请求从用户在浏览器中点击链接到页面呈现在屏幕上,在此期间到底发生了什么?浏览器请求 Web 页面时,会执行重复流程,获取在屏幕上绘制页面需要的所有信息。为了更容易理解,我们把这一过程分成两部分:资源获取、页面解析/渲染。资源请求流程图:流程为:把待请求 URL 放入队列解析 URL 中域名的 IP 地址(A)建立与目标主机的 TCP 连接(B)如果是 HTTPS 请求,初始化并完成 TLS 握手(C)向页面对应的 URL 发送请求。资源响应流程图:接收响应如果(接收的)是主体 HTML,那么解析它,并针对页面中的资源触发优先获取机制(A)如果页面上的关键资源已经接收到,就开始渲染页面(B)接收其他资源,继续解析渲染,直到结束(C)页面上的每一次点击,都需要重复执行前面那些流程,给网络带宽和设备资源带来压力。Web 性能优化的的核心,就是加快甚至干脆去掉其中的某些步骤。3.1.2 关键性能指标下面网络级别的性能指标,它会影响整个 Web 页面加载。延迟: 指 IP 数据包从一个网络端点到另一个网络端点所花费的时间。带宽: 只要带宽没有饱和,两个网络端点之间的连接会一次处理尽可能多的数据量。DNS查询: 在客户端能够获取 Web 页面前,它需要通过域名系统(DNS)把主机名称转换成 IP 地址。建立连接时间: 在客户端和服务器之间建立连接需要三次握手。握手时间一般与客户端和服务器之间的延迟有关。TLS协商时间: 如果客户端发起 HTTPS 连接,它还需要进行传输层安全协议(TLS)协商,TLS 会造成额外的往返传输。首字节时间(TTFB): TTFB 是指客户端从开始定位到 Web 页面,至接收到主体页面响应的第一字节所耗费的时间。它包含了之前提到的各种耗时,还要加上服务器处理时间。对于主体页面上的资源,TTFB 测量的是从浏览器发起请求至收到其第一字节之间的耗时。内容下载时间: 等同于被请求资源的最后字节到达时间(TTLB)。开始渲染时间: 客户端的屏幕上什么时候开始显示内容?这个指标测量的是用户看到空白页面的时长。文档加载完成时间: 这是客户端浏览器认为页面加载完毕的时间。3.1.3 HTTP/1 的问题HTTP/1 的问题自然是 HTTP/2 要解决的核心问题1. 队头阻塞浏览器很少只从一个域名获取一份资源,一般希望能同时获取许多资源。h1 有个特性叫管道化(pipelining),允许一次发送一组请求,但是只能按照发送顺序依次接收响应。管道化备受互操作性和部署的各种问题的困扰,基本没有实用价值。在请求应答过程中,如果出现任何状况,剩下所有的工作都会被阻塞在那次请求应答之后。这就是『队头阻塞』,它会阻碍网络传输和 Web 页面渲染,直至失去响应。为了防止这种问题,现代浏览器会针对单个域名开启 6 个连接,通过各个连接分别发送请求。它实现了某种程度上的并行,但是每个连接仍会受到的影响。2. 低效的 TCP 利用传输控制协议(TCP)的设计思路是:对假设情况很保守,并能够公平对待同一网络的不同流量的应用。涉及的核心概念就是拥塞窗口(congestion window)。『拥塞窗口』是指,在接收方确认数据包之前,发送方可以发出的 TCP 包的数量。 例如,如果拥塞窗口指定为 1,那么发送方发出 1 个数据包之后,只有接收方确认了那个包,才能发送下一个。TCP 有个概念叫慢启动(Slow Start),它用来探索当前连接对应拥塞窗口的合适大小。慢启动的设计目标是为了让新连接搞清楚当前网络状况,避免给已经拥堵的网络继续添乱。它允许发送者在收到每个确认回复后额外发送 1 个未确认包。这意味着新连接在收到 1 个确认回复之后,可以发送 2 个数据包;在收到 2 个确认回复之后,可以发 4 个,以此类推。这种几何级数增长很快到达协议规定的发包数上限,这时候连接将进入拥塞避免阶段。这种机制需要几次往返数据请求才能得知最佳拥塞窗口大小。但在解决性能问题时,就这区区几次数据往返也是非常宝贵的时间成本。如果你把一个数据包设置为最大值下限 1460 字节,那么只能先发送 5840 字节(假定拥塞窗口为 4),然后就需要等待接收确认回复。理想情况下,这需要大约 9 次往返请求来传输完整个页面。除此之外,浏览器一般会针对同一个域名开启 6 个并发连接,每个连接都免不了拥塞窗口调节。传统 TCP 实现利用拥塞控制算法会根据数据包的丢失来反馈调整。如果数据包确认丢失了,算法就会缩小拥塞窗口。这就类似于我们在黑暗的房间摸索,如果腿碰到了桌子就会马上换个方向。如果遇到超时,也就是等待的回复没有按时抵达,它甚至会彻底重置拥塞窗口并重新进入慢启动阶段。新的算法会把其他因素也考虑进来,例如延迟,以提供更妥善的反馈机制。前面提到过,因为 h1 并不支持多路复用,所以浏览器一般会针对指定域名开启 6 个并发连接。这意味着拥塞窗口波动也会并行发生 6 次。TCP 协议保证那些连接都能正常工作,但是不能保证它们的性能是最优的。3. 臃肿的消息首部虽然 h1 提供了压缩被请求内容的机制,但是消息首部却无法压缩。消息首部可不能忽略,尽管它比响应资源小很多,但它可能占据请求的绝大部分(有时候可能是全部)。如果算上 cookie,就更大了。消息首部压缩的缺失也容易导致客户端到达带宽上限,对于低带宽或高拥堵的链路尤其如此。『体育馆效应』(Stadium Effect)就是一个经典例子。如果成千上万人同一时间出现在同一地点(例如重大体育赛事),会迅速耗尽无线蜂窝网络带宽。这时候,如果能压缩请求首部,把请求变得更小,就能够缓解带宽压力,降低系统的总负载。4. 受限的优先级设置如果浏览器针对指定域名开启了多个 socket(每个都会受队头阻塞问题的困扰),开始请求资源,这时候浏览器能指定优先级的方式是有限的:要么发起请求,要么不发起。然而 Web 页面上某些资源会比另一些更重要,这必然会加重资源的排队效应。这是因为浏览器为了先请求优先级高的资源,会推迟请求其他资源。但是优先级高的资源获取之后,在处理的过程中,浏览器并不会发起新的资源请求,所以服务器无法利用这段时间发送优先级低的资源,总的页面下载时间因此延长了。还会出现这样的情况:一个高优先级资源被浏览器发现,但是受制于浏览器处理的方式,它被排在了一个正在获取的低优先级资源之后。5. 第三方资源如今 Web 页面上请求的很多资源完全独立于站点服务器的控制,我们称这些为第三方资源。现代 Web 页面加载时长中往往有一半消耗在第三方资源上。虽然有很多技巧能把第三方资源对页面性能的影响降到最低,但是很多第三方资源都不在 Web 开发者的控制范围内,所以很可能其中有些资源的性能很差,会延迟甚至阻塞页面渲染。令人扫兴的是, h2 对此也束手无策。3.2 Web性能优化技术2010 年,谷歌把 Web 性能作为影响页面搜索评分的重要因素之一,性能指标开始在搜索引擎中发挥作用。对于很多 Web 页面,浏览器的大块时间并不是用于呈现来自网站的主体内容(通常是 HTML),而是在请求所有资源并渲染页面。因此,Web 开发者逐渐更多地关注通过减少客户端网络延迟和优化页面渲染性能来提升Web 性能。3.2.1 Web性能的最佳实践1. DNS 查询优化在与服务主机建立连接之前,需要先解析域名;那么,解析越快就越好。下面有一些方法:限制不同域名的数量。当然,这通常不是你能控制的保证低限度的解析延迟。了解你的 DNS 服务基础设施的结构,然后从你的最终用户分布的所有地域定期监控解析时间在 HTML 或响应中利用 DNS 预取指令。这样,在下载并处理 HTML 的同时,预取指令就能开始解析页面上指定的域名 <link rel=“dns-prefetch” href="//ajax.googleapis.com">2. 优化 TCP 连接本章前面提到过,开启新连接是一个耗时的过程。如果连接使用 TLS(也确实应该这么做),开销会更高。降低这种开销的方法如下尽早终止并响应。借助 CDN,在距离请求用户很近的边缘端点上,请求就可以获得响应,所以可以终止连接,大幅减少建立新连接的通信延迟。实施最新的 TLS 最佳实践来优化 HTTPS。利用 preconnect 指令,连接在使用之前就已经建立好了,这样处理流程的关键路径上就不必考虑连接时间了,preconnect 不光会解析 DNS,还会建立 TCP 握手连接和 TLS 协议(如果需要)<link rel=“preconnect” href="//fonts.example.com" crossorigin>如果要从同一个域名请求大量资源,浏览器将自动开启到服务器的并发连接,避免资源获取瓶颈。虽然现在大部分浏览器支持 6 个或更多的并发连接数目,但你不能直接控制浏览器针对同一域名的并发连接数。3. 避免重定向重定向通常触发与额外域名建立连接。在移动网络中,一次额外的重定向可能把延迟增加数百毫秒,这不利于用户体验,并最终会影响到网站上的业务。简单的解决方案就是彻底消灭重定向,因为对于重定向的使往往并没有合理原因。如果它们不能被直接消灭,你还有两个选择:利用 CDN 代替客户端在云端实现重定向如果是同一域名的重定向,使用 Web 服务器上的 rewrite 规则,避免重定向4. 客户端缓存没有什么比直接从本地缓存获取资源来得更快,因为它根本就不需要建立网络连接。所谓的纯静态内容,例如图片或带版本的数据,可以在客户端永久缓存。即便 TTL 被设置得很长,比如一个月,它还是会因为缓存提早回收或清理而过期,这时客户端可能不得不从源头再次获取。CSS/JS 和个性化资源,缓存时间大约是会话(交互)平均时间的两倍。这段时间足够长,保证大多数用户在浏览网站时能够从本地拉取资源;同时也足够短,几乎能保证下次会话时从网络上拉取最新内容。可以通过 HTTP 首部指定 cache control 以及键 max-age(以秒为单位),或者 expires 首部。5. 网络边缘的缓存个人信息(用户偏好、财务数据等)绝对不能在网络边缘缓存,因为它们不能共享。时间敏感的资源也不应该缓存,例如实时交易系统上的股票报价。这就是说,除此之外其他一切都是可以缓存的,即使仅仅缓存几秒或几分钟。对于那些不是经常更新,然而一旦有变化就必须立刻更新的资源,例如重大新闻,可以利用各大 CDN 厂商提供的缓存清理(purging)机制处理。这种模式被称为『一直保留,直到被通知』(Hold til Told),意思是永久缓存这些资源,等收到通知后才删除。6. 条件缓存如果缓存 TTL 过期,客户端会向服务器发起请求。在多数情况下,收到的响应其实和缓存的版本是一样的,重新下载已经在缓存里的内容也是一种浪费。HTTP 提供条件请求机制,客户端能以有效方式询问服务器:『如果内容变了,请返回内容本身;否则,直接告诉我内容没变。』当资源不经常变化时,使用条件请求可以显著节省带宽和性能;但是,保证资源的最新版迅速可用也是非常重要的。使用条件缓存可以通过以下方法。在请求中包含 HTTP 首部 Last-Modified-Since。仅当最新内容在首部中指定的日期之后被更新过,服务器才返回完整内容;否则只返回 304 响应码,并在响应首部中附带上新的时间戳 Date 字段。在请求体中包含实体校验码,或者叫 ETag;它唯一标识所请求的资源。ETag 由服务器 提供,内嵌于资源的响应首部中。服务器会比较当前 ETag 与请求首部中收到的 ETag,如果一致,就只返回 304 响应码;否则返回完整内容。一般来说,大多数 Web 服务器会对图片和 CSS/JS 使用这些技术,但你也可以将其用到其他资源。7. 压缩和代码极简化所有的文本内容(HTML、JS、CSS、SVG、XML、JSON、字体等),都可以压缩和极简化。这两种方法组合起来,可以显著减少资源大小。更少字节数对应着更少的请求与应答,也就意味着更短的请求时间。极简化(minification, 混淆)是指从文本资源中剥离所有非核心内容的过程。通常,要考虑方便人类阅读和维护,而浏览器并不关心可读性,放弃代码可读性反而能节省空间。在极简化的基础上,压缩可以进一步减少字节数。它通过可无损还原的算法减少资源大小。在发送资源之前,如果服务器进行压缩处理,可以节省 90% 的大小。8. 避免阻塞 CSS/JS在屏幕上绘制第一个像素之前,浏览器必须确保 CSS 已经下载完整。尽管浏览器的预处理器很智能,会尽早请求整个页面所需要的 CSS,但是把 CSS 资源请求放在页面靠前仍然是种最佳实践,具体位置是在文档的 head 标签里,而且要在任何 JS 或图片被请求和处理之前。默认情况下,如果在 HTML 中定位了 JS,它就会被请求、解析,然后执行。在浏览器处理完这个 JS 之前,会阻止其后任何资源的下载渲染。然而大多数时候,这种默认的阻塞行为导致了不必要的延迟,甚至会造成单点故障。为了减轻 JS 阻塞带来的潜在影响,下面针对己方资源(你能控制的)和第三方资源(你不能控制的)推荐了不同的策略定期校验这些资源的使用情况。随着时间的变迁,Web 页面可能会持续下载一些不再需要的 JS,这时最好去掉它。如果 JS 执行顺序无关紧要,并且必须在 onload 事件触发之前运行,那么可以设置 async 属性,像这样:<script async src="/js/myfile.js">只需做到下载 JS 与解析 HTML 并行,就能极大地提升整体用户体验。慎用 document.write 指令,因为很可能中断页面执行,所以需要仔细测试。如果 JS 执行顺序很重要,并且你也能承受脚本在 DOM 加载完之后运行,那么请使用 defer 属性。像这样<script defer src="/js/myjs.js">对不会影响到页面初次展示的 JS 脚本,必须在 onload 事件触发之后请求(处理)它。如果你不想延迟主页面的 onload 事件,可以考虑通过 iframe 获取 JS,因为它的处理独立于主页面。但是,通过 iframe 下载的 JS 访问不了主页面上的元素。9. 图片优化对大多数网站而言,图片的重要性和比重在不断增加。既然图片主导了多数现代网站,优化它们就能够获得最大的性能回报。所有图片优化手段的目标都是在达到指定视觉质量的前提下传输最少的字节。图片元信息,例如题材地理位置信息、时间戳、尺寸和像素信息,通常包含在二进制数据里,应该在发送给客户端之前去掉(务必保留版权和色彩描述信息)。这种无损处理能够在图片生成时完成。对于 PNG 图片,一般会节省大概 10% 的空间。图片过载(image overloading)是指,图片最终被浏览器自动缩小,要么因为原始尺寸超过了浏览器可视区中的占位大小,要么因为像素超过设备的显示能力。这不仅浪费带宽,消耗的 CPU 资源也很可观,这些计算资源有时在手持设备上相当宝贵。想要解决图片过载,可以使用技术手段,针对用户的设备、网络状况和预期的视觉质量,提供裁剪过的图片(就尺寸和质量而言)。3.2.2 反模式HTTP/2 对每个域名只会开启一个连接,所以 HTTP/1.1 下的一些诀窍对它来说只会适得其反。详细看 6.7 节3.3 小结HTTP/1.1 孕育了各种性能优化手段与诀窍,可以帮助我们深入理解 Web 及其内部实现。HTTP/2 的目标之一就是淘汰掉众多(并不是全部)此类诀窍。4. HTTP/2 迁移在升级到 HTTP/2 之前,你应该考虑:浏览器对 h2 的支持情况迁移到 TLS(HTTPS)的可能性对你的网站做基于 h2 的优化(可能对 h1 有反作用)网站上的第三方资源保持对低版本客户端的兼容4.1 浏览器的支持情况任何不支持 h2 的客户端都将简单地退回到 h1,并仍然可以访问你的站点基础设施。4.2 迁移到 TLS所有主流浏览器只能访问基于 TLS(即 HTTPS 请求)的 h2。4.3 撤销针对 HTTP/1.1 的优化Web 开发者之前花费了大量心血来充分使用 h1,并且已经总结了一些诀窍,例如资源合并、域名拆分、极简化、禁用 cookie 的域名、生成精灵图,等等。所以,当得知这些实践中有些在 h2 下变成反模式时,你可能会感到吃惊。例如,资源合并(把很多 CSS/JS 文件拼合成一个)能避免浏览器发出多个请求。对 h1 而言这很重要,因为发起请求的代价很高;但是在 h2 的世界里,这部分已经做了深度优化。放弃资源合并的结果可能是,针对单个资源发起请求的代价很低,但浏览器端可以进行更细粒度的缓存。详细看 6.7 节5. HTTP/2 协议本章将全面探讨 HTTP/2 的底层工作原理,深入到数据层传输的帧及其通信方式。5.1 HTTP/2 分层HTTP/2 大致可以分为两部分分帧层 即 h2 多路复用能力的核心部分,主要目的还是传输 HTTP数据或 http 层 其中包含传统上被认为是 HTTP 及其关联数据的部分,向后兼容 HTTP/1.1h2 有些特点需要关注一下:二进制协议 :h2 的分帧层是基于帧的二进制协议,这方便了机器解析,但是肉眼识别比较困难首部压缩 :仅使用二进制协议还不够,h2 的首部还会被深度压缩。这将显著减少传输中的冗余字节多路复用 :在调试工具里查看基于 h2 传输的连接的时候,你会发现请求和响应交织在一起加密传输 :线上传输的绝大部分数据是加密过的,所以在中途读取会更加困难5.2 连接与完全无状态的 h1 不同的是,h2 把它所承载的帧(frame)和流(stream)共同依赖的连接层元素捆绑在一起,其中既包含连接层设置也包含首部表。也就是说,与之前的 HTTP 版本不同,每个 h2 连接都有一定的开销。之所以这么设计,是考虑到收益远远超过其开销。在连接的时候,HTTP/2 提供两种协议发现的机制:在连接不加密的情况下,客户端会利用 Upgrade 首部来表明期望使用 h2。如果服务器也可以支持 h2,它会返回一个101 Switching Protocols(协议转换)响应。这增加了一轮完整的请求-响应通信如果连接基于 TLS,情况就不同了。客户端在 ClientHello 消息中设置 ALPN (Application-Layer Protocol Negotiation,应用层协议协商)扩展来表明期望使用 h2 协议,服务器用同样的方式回复。如果使用这种方式,那么 h2 在创建 TLS 握手的过程中完成协商,不需要多余的网络通信。在协议制定过程中,很早就把小数点去掉了,这表明未来的 HTTP 版本不能保证语义的向后兼容,也就是说只有 HTTP/2 没有 HTTP/2.0、HTTP/2.25.3 帧HTTP/2 是基于帧(frame)的协议,采用分帧是为了将重要信息都封装起来,让协议的解析方可以轻松阅读、解析并还原信息。 相比之下,h1 不是基于帧的,而是以文本分隔。所以解析 h1 的请求或响应可能会出现以下问题:一次只能处理一个请求或响应,完成之前不能停止解析无法预判解析需要多少内存。这会带来一系列问题:你要把一行读到多大的缓冲区里;如果行太长会发生什么;应该增加并重新分配内存,还是返回 400 错误从另一方面来说,有了帧,处理协议的程序就能预先知道会收到什么。下图是一个 HTTP/2 帧的结构前 9 个字节对于每个帧是一致的。解析时只需要读取这些字节,就可以准确地知道在整个帧中期望的字节数。其中每个字段的说明如下名称长度描述Length3 字节表示帧负载的长度(取值范围为 2^142^24-1 字节)。 请注意,214 字节是默认的最大帧大小,如果需要更大的帧,必须在 SETTINGS 帧中设置Type1 字节当前帧类型Flags1 字节具体帧类型的标识R1 位保留位,不要设置,否则可能带来严重后果Stream Identifier31 位每个流的唯一 IDFrame Payload长度可变真实的帧内容,长度是在 Length 字段中设置的相比依靠分隔符的 h1,h2 还有另一大优势:如果使用 h1 的话,你需要发送完上一个请求或者响应,才能发送下一个;由于 h2 是分帧的,请求和响应可以交错甚至多路复用。多路复用有助于解决类似队头阻塞的问题。h2 有十种不同的帧类型:名称ID (Type)描述DATA0x0数据帧,传输流的核心内容HEADERS0x1报头帧,包含 HTTP 首部,和可选的优先级参数PRIORITY0x2优先级帧,指示或者更改流的优先级和依赖RST_STREAM0x3流终止帧,允许一端停止流(通常由于错误导致的)SETTINGS0x4设置帧,协商连接级参数PUSH_PROMISE0x5推送帧,提示客户端,服务器要推送些东西PING0x6PING 帧,测试连接可用性和往返时延(RTT)GOAWAY0x7GOAWAY 帧,告诉另一端,当前端已结束WINDOW_UPDATE0x8窗口更新帧,协商一端将要接收多少字节(用于流量控制)CONTINUATION0x9延续帧,用以扩展 HEADER 数据块扩展帧 :HTTP/2 内置了名为扩展帧的处理新的帧类型的能力。依靠这种机制,客户端和服务器的实现者可以实验新的帧类型,而无需制定新协议。按照规范,任何客户端不能识别的帧都会被丢弃,所以网络上新出现的帧就不会影响核心协议。当然,如果你的应用程序依赖于新的帧,而中间代理会丢弃它,那么可能会出现问题。5.4 流HTTP/2 规范中的流(stream):HTTP/2 连接上独立的、双向的帧序列交换。你可以将流看作在连接上的一系列帧,用来传输一对请求/响应消息。如果客户端想要发出请求,它会开启一个新的流,服务器也在这个流上回复。这与 h1 的请求、响应流程类似,区别在于,因为有分帧,所以多个请求和响应可以交错,而不会互相阻塞。流 ID(帧首部的第 6~9 字节)用来标识帧所属的流。客户端到服务器的 h2 连接建立之后,通过发送 HEADERS 帧来启动新的流,如果首部需要跨多个帧,可能还发会送 CONTINUATION 帧。5.4.1 消息HTTP 消息泛指 HTTP 请求或响应。一个消息至少由 HEADERS 帧(用来初始化流)组成,并且可以另外包含 CONTINUATION 和 DATA 帧,以及其他的 HEADERS 帧。 下图是普通 GET 请求的示例流程POST 和 GET 的主要差别之一就是 POST 请求通常包含客户端发出的大量数据。下图是 POST 消息对应的各帧可能的样子h1 的请求和响应都分成消息首部和消息体两部分;与之类似,h2 的请求和响应分成 HEADERS 帧和 DATA 帧。HTTP/1 和 HTTP/2 消息的下列差别是需要注意一切都是header :h1 把消息分成请求/状态行、首部两部分。h2 取消了这种区分,并把这些行变成了魔法伪首部没有分块编码(chunked encoding) :只有在无法预先知道数据长度的情况下向对方发送数据时,才会用到分块。在使用帧作为核心协议的 h2 里,不再需要分块不再有101的响应 :Switching Protocol 响应是 h1 的边缘应用。它如今最常见的应用可能就是用以升级到 WebSocket 连接。ALPN 提供了更明确的协议协商路径,往返的开销也更小5.4.2 流量控制h2 的新特性之一是基于流的流量控制。h1 中只要客户端可以处理,服务端就会尽可能快地发送数据,h2 提供了客户端调整传输速度的能力,服务端也同样可以调整传输的速度。WINDOW_UPDATE 帧用于执行流量控制功能,可以作用在单独某个流上(指定具体 Stream Identifier)也可以作用整个连接 (Stream Identifier 为 0x0),只有 DATA 帧受流量控制影响。初始化流量窗口后,发送多少负载,流量窗口就减少多少,如果流量窗口不足就无法发送,WINDOW_UPDATE 帧可以增加流量窗口大小。流建立的时候,窗口大小默认 65535(2^16-1)字节。5.4.3 优先级流的最后一个重要特性是依赖关系。现代浏览器会尽量以最优的顺序获取资源,由此来优化页面性能。在没有多路复用的时候,在它可以发出对新对象的请求之前,需要等待前一个响应完成。有了 h2 多路复用的能力,客户端就可以一次发出所有资源的请求,服务端也可以立即着手处理这些请求。由此带来的问题是,浏览器失去了在 h1 时代默认的资源请求优先级策略。假设服务器同时接收到了 100 个请求,也没有标识哪个更重要,那么它将几乎同时发送每个资源,次要元素就会影响到关键元素的传输。h2 通过流的依赖关系来解决上面这个问题。通过 HEADERS 帧和 PRIORITY 帧,客户端可以明确地和服务端沟通它需要什么,以及它需要这些资源的顺序。这是通过声明依赖关系树和树里的相对权重实现的。依赖关系 为客户端提供了一种能力,通过指明某些对象对另一些对象有依赖,告知服务器这些对象应该优先传输权重 让客户端告诉服务器如何确定具有共同依赖关系的对象的优先级流可以被标记为依赖其他流,所依赖的流完成后再处理当前流。每个依赖 (dependency) 后都跟着一个权重 (weight),这一数字是用来确定依赖于相同的流的可分配可用资源的相对比例。其他时候也可以通过 PRIORITY 帧调整流优先级。设置优先级的目的是为了让端点表达它所期望对端在并发的多个流之间如何分配资源的行为。更重要的是,当发送容量有限时,可以使用优先级来选择用于发送帧的流。5.5 服务端推送升单个对象性能的最佳方式,就是在它被用到之前就放到浏览器的缓存里面。这正是 h2 服务端推送的目的。5.5.1 推送对象如果服务器决定要推送一个对象(RFC 中称为『推送响应』),会构造一个 PUSH_PROMISE 帧。这个帧有很多重要属性:PUSH_PROMISE 帧首部中的流 ID (Promised Stream ID)用来响应相关联的请求。推送的响应一定会对应到客户端已发送的某个请求。如果浏览器请求一个主体 HTML 页面,如果要推送此页面使用的某个 JavaScript 对象,服务器将使用请求对应的流 ID 构造 PUSH_PROMISE 帧。PUSH_PROMISE 帧的首部块与客户端请求推送对象时发送的首部块是相似的。所以客户端有办法放心检查将要发送的请求。被发送的对象必须确保是可缓存的。:method 首部的值必须确保安全。安全的方法就是幂等的那些方法,这是一种不改变任何状态的好办法。例如,GET 请求被认为是幂等的,因为它通常只是获取对象,而 POST 请求被认为是非幂等的,因为它可能会改变服务器端的状态。理想情况下,PUSH_PROMISE 帧应该更早发送,应当早于客户端接收到可能承载着推送对象的 DATA 帧。假设服务器要在发送 PUSH_PROMISE 之前发送完整的 HTML,那客户端可能在接收到 PUSH_PROMISE 之前已经发出了对这个资源的请求。h2 足够健壮,可以优雅地解决这类问题,但还是会有些浪费。PUSH_PROMISE 帧会指示将要发送的响应所使用的流 ID如果客户端对 PUSH_PROMISE 的任何元素不满意,就可以按照拒收原因选择重置这个流(使用 RST_STREAM),或者发送 PROTOCOL_ERROR (在 GOAWAY 帧中)。常见的情况是缓存中已经有了这个对象。假设客户端不拒收推送,服务端会继续进行推送流程,用 PUSH_PROMISE 中指明 ID 对应的流来发送对象 5.5.2 选择要推送的资源如果服务器接收到一个页面的请求,它需要决定是推送页面上的资源还是等客户端来请求。决策的过程需要考虑到如下方面资源已经在浏览器缓存中的概率从客户端看来,这些资源的优先级 (参见 5.4.3 节)可用的带宽,以及其他类似的会影响客户端接收推送的资源如果服务器选择正确,那就真的有助于提升页面的整体性能,反之则会损耗页面性能。5.6 首部压缩现代网页平均有很多请求,这些请求之间几乎没有新的的内容,这是极大的浪费。首部列表 (Header List) 是零个或多个首部字段 (Header Field) 的集合。当通过连接传送时,首部列表通过压缩算法(即下文 HPACK) 序列化成首部块 (Header Block),不用 GZIP 是因为它有泄漏加密信息的风险。HPACK 是种表查找压缩方案,它利用霍夫曼编码获得接近 GZIP 的压缩率。然后,序列化的首部块又被划分成一个或多个叫做首部块片段 (Header Block Fragment) 的字节序列,并通过 HEADERS、PUSH_PROMISE,或者 CONTINUATION 帧进行有效负载传送。假设客户端按顺序发送如下请求首部:Header1: fooHeader2: barHeader3: bat当客户端发送请求时,可以在首部数据块中指示特定首部及其应该被索引的值。它会创建一张表:索引首部名称值62Header1foo63Header2bar64Header3bat如果服务端读到了这些请求首部,它会照样创建一张表。客户端发送下一个请求的时候, 如果首部相同,它可以直接发送:62 63 64 ,服务器会查找先前的表格,并把这些数字还原成索引对应的完整首部。首部压缩机制中每个连接都维护了自己的状态。HPACK 的实现比这个要复杂得多,比如:请求端和响应端各维护了两张表格。其中之一是动态表,创建方法和上面差不 多。另一张是静态表,它由 61 个最常见的首部的键值组合而成。例如 :method: GET 在静态表中索引为 2。按规定,静态表包含 61 个条目,所以上例索引编号从 62 开始。关于字段如何索引,有很多控制规则:发送索引编号和文本值仅发送文本值,不对它们进行索引(对于一次性或敏感首部)发送索引的首部名,值用文本表示,但不进行索引处理(如:path: /foo.html,其值每次都不同)发送索引过的首部名和值(如上例中的第二个请求)使用打包方案的整数压缩,以实现极高的空间效率利用霍夫曼编码表进一步压缩字符串5.7 线上传输线上传输的 h2 信息是经过压缩的二进制数据。一个简单的GET请求下面是一个简单的 h2 的 get 请求:authority: www.akamai.com:method: GET:path: /:scheme: httpsaccept: text/html,application/xhtml+xml,…accept-language: en-US,en;q=0.8cookie: sidebar_collapsed=0; _mkto_trk=…upgrade-insecure-requests: 1user-agent: Mozilla/5.0 (Macintosh;…下面是 h2 的一个响应:status: 200cache-control: max-age=600content-encoding: gzipcontent-type: text/html;charset=UTF-8date: Tue, 31 May 2016 23:38:47 GMTetag: “08c024491eb772547850bf157abb6c430-gzip"expires: Tue, 31 May 2016 23:48:47 GMTlink: <https://c.go-mpulse.net>;rel=preconnectset-cookie: ak_bmsc=8DEA673F92AC…vary: Accept-Encoding, User-Agentx-akamai-transformed: 9c 237807 0 pmb=mRUM,1x-frame-options: SAMEORIGIN在这个响应中,服务器表示请求已成功受理(状态码 200),设置了 cookie(cookie 首部),表示返回的内容使用 gzip 压缩(content-encoding 首部)6. HTTP/2性能HTTP/2 大部分情况下传输 web 页面比 HTTP/1.1 快。对于包含很多小型资源的页面,h2 加载页面的时间比 h1 更短。这是因为 h1 下(有 6 个 TCP 连接)服务器只能并行发送 6 个资源(由于队头阻塞),而 h2 下多个流(stream)可以共用连接。进一步说,随着网络条件变差,h1 和 h2 下页面加载时间(PLT)都会增加;但是 h2 由于单连接,如果唯一的连接发生了丢包,所有工作都会受影响。对于包含少量大型资源的页面,在所有网络条件下,h1 性能上都比 h2 表现要好。这个多少令人感到意外的结果是初始拥塞窗口导致的。如果开启 6 个连接,h1 的初始拥塞窗口大小实际上是 h2 的 6 倍。因此在会话开始阶段,h2 的连接窗口尚未增长到最佳值,但 h1 早就能更快地传输更多数据了。这个问题目前仍在解决,因为它导致初始拥塞窗口对 h2 而言太小,然而对 h1 而言又太大。 此外,h2 比 h1 更容易受丢包的影响。对于包含一些极大型资源的 Web 页面,两者没有任何差异。h2 的初始拥塞窗口劣势被整体下载时长掩盖了,多路复用此时也不再具有优势。6.1 客户端实现网络条件相同,使用不同浏览器客户端,同样的网站页面加载性能可能差别很大。协议的具体实现很重要并非所有请求在任何情况下都会从 HTTP/2 受益,即便如此,URL 使用 h2 后性能提升的比例也依旧高于下降的比例6.2 延迟延迟是指数据包从一个端点到另一个端点所花的时间。有时,它也表示数据包到达接收方然后返回发送方所需的时间,又称为往返时延(RTT),长度一般以毫秒计。 影响延迟的因素众多,但有两个是最重要的:端点间的距离,以及所用传输介质两点之间的网线不会是笔直的,另外各种网关、路由器、交换机以及移动基站等(也包括服务器应用本身)都会增加延迟6.3 丢包如果网络中传输的数据包没有成功到达目的地,就会发生丢包,这通常是由网络拥堵造成的。频繁丢包会影响 h2 的页面传输,主要是因为 h2 开启单一 TCP 连接,每次有丢包/拥堵时,TCP 协议就会缩减 TCP 窗口。如果 TCP 流上丢了一个数据包,那么整个 h2 连接都会停顿下来,直到该数据包重发并被接收到。6.4 服务端推送服务端推送让服务器具备了在客户端请求之前就推送资源的能力。 测试表明,如果合理使用推送,页面渲染时间可以减少 20%50%。 然而,推送也会浪费带宽,这是因为服务端可能试图推送那些在客户端已经缓存的资源,导致客户端收到并不需要的数据。客户端确实可以发送 RST_STREAM 帧来拒绝服务器的 PUSH_PROMISE 帧,但是 RST_STREAM 并不会即刻到达,所以服务器还是会发送一些多余的信息。如果用户第一次访问页面时,就能向客户端推送页面渲染所需的关键 CSS 和 JS 资源,那么服务端推送的真正价值就实现了。不过,这要求服务器端实现足够智能,以避免『推送承诺』(push promise)与主体 HTML 页面传输竞争带宽。理想情况下,服务端正在处理 HTML 页面主体请求时才会发起推送。有时候,服务端需要做一些后台工作来生成 HTML 页面。这时候服务端在忙,客户端却在等待,这正是开始向客户端推送所需资源的绝佳时机。6.5 首字节时间首字节时间(TTFB)用于测量服务器的响应能力。是从客户端发起 HTTP 请求到客户端浏览器收到资源的第一个字节所经历的时间。由 socket 连接时间、发送 HTTP 请求所需时间、收到页面第一个字节所需时间组成。h1 中,客户端针对单个域名在每个连接上依次请求资源,而且服务器会按序发送这些资源。客户端只有接收了之前请求的资源,才会再请求剩下的资源,服务器接着继续响应新的资源请求。这个过程会一直重复,直到客户端接收完渲染页面所需的全部资源。与 h1 不同,通过 h2 的多路复用,客户端一旦加载了 HTML,就会向服务器并行发送大量请求。相比 h1,这些请求获得响应的时间之和一般会更短;但是因为是请求是同时发出的,而单个请求的计时起点更早,所以 h2 统计到的 TTFB 值会更高。HTTP/2 比 h1 确实做了更多的工作,其目的就是为了从总体上提升性能。下面是一些 h1 没有,但 h2 实现了的窗口大小调节依赖树构建维持首部信息的静态 / 动态表压缩 / 解压缩首部优先级调整(h2 允许客户端多次调整单一请求的优先级)预先推送客户端尚未请求的数据流下图是使用 h1 和 h2 加载同一个页面的加载时序对比,总体来说 h2 体验更好6.6 第三方资源许多网站会使用各种统计、跟踪、社交以及广告平台,就会引入各种第三方的资源。第三方请求往往通过不同域名发送;由于浏览器需要解析 DNS、建立 TCP 连接、协商 TLS,这将严重影响性能;因为第三方资源在不同域名下,所以请求不能从服务端推送、资源依赖、请求优先级等 h2 特性中受益。这些特性仅是为请求相同域名下的资源设计的;你无法控制第三方资源的性能,也无法决定它们是否会通过 h2 传输;6.7 HTTP/2反模式h1 下的一些性能调优办法在 h2 下会起到反作用。下面列出了一些用于优化 h1 请求的常用技巧,并标注了 h2 方面的考虑。名称描述备注资源合并把多个文件(JavaScript、CSS) 合成一个文件,以减少 HTTP 请求在 HTTP/2 下这并非必要,因为请求的传输字节数和时间成本更低,虽然这种成本仍然存在极简化去除 HTML、JavaScript、CSS 这类文件中无用的代码很棒的做法,在 HTTP/2 下也要保留域名拆分把资源分布到不同的域名上面去,让浏览器利用更多的 socket 连接HTTP/2 的设计意图是充分利用单个 socket 连接,而拆分域名会违背这种意图。建议取消域名拆分,但请注意本表格之后的附注框会介绍这个问题相关的各种复杂情况禁用 cookie 的域名为图片之类的资源建立单独的域名,这些域名不用 cookie,以尽可能减少请求尺寸应该避免为这些资源单独设立域名(参见域名拆分),但更重要的是,由于 HTTP/2 提供了首部压缩,cookie 的开销会显著降低生成精灵图把多张图片拼合为一个文件,使用 CSS 控制在 Web 页面上展示的部分与极简化类似,只不过用 CSS 实现这种效果的代价高昂;不推荐在 HTTP/2 中使用6.7.1 生成精灵图和资源合并/内联精灵图(spriting)是指把很多小图片拼合成一张大图,这样只需发起一个请求就可以覆盖多个图片元素。在 HTTP/2 中,针对特定资源的请求不再是阻塞式的,很多请求可以并行处理;就性能而言,生成精灵图已失去意义,因为多路复用和首部压缩去掉了大量的请求开销。与之类似,小的文本资源,例如 JS 和 CSS,会依照惯例合并成一份更大的资源,或者直接内嵌在主体 HTML 中,这也是为了减少客户端-服务器连接数。这种做法有个问题是,那些小的 CSS 或 JS 自身也许可缓存,但如果它们内嵌在不可缓存的 HTML 中的话,当然也就不可缓存了。把很多小的 JS 脚本合并成一个大文件可能仍旧对 h2 有意义,因为这样可以更好地压缩处理并节省 CPU。6.7.2 域名拆分域名拆分(sharding)是为了利用浏览器针对每个域名开启多个连接的能力来并行下载资源。对于包含大量小型资源的网站,普遍的做法是拆分域名,以利用现代浏览器针能对每个域名开启 6 个连接的特性,充分利用可用带宽。因为 HTTP/2 采取多路复用,所以域名拆分就不是必要的了,并且反而会让协议力图实现的目标落空。比较好的办法就是继续保持当前的域名拆分,但是确保这些域名共享同一张证书 [ 通配符 / 存储区域网络(SAN)],并保持服务器 IP 地址和端口相同,以便从浏览器网络归并(network coalescence)中收益,这样可以节省为单个域名连接建立的时间。6.7.3 禁用cookie的域名在 HTTP/1 下,请求和响应首部从不会被压缩。随着时间推移,首部大小已经增长了,超过单个 TCP 数据包的 cookie 可以说司空见惯。因此,在内容源和客户端之间来回传输首部信息的开销可能造成明显的延迟。 因此,对图片之类不依赖于 cookie 的资源,设置禁用 cookie 的域名是个合理的建议。 但是 HTTP/2 中,首部是被压缩的,并且客户端和服务器都会保留『首部历史』,避免重复传输已知信息。所以,如果你要重构站点,大可不必考虑禁用 cookie 的域名,这样能减少很多包袱。 静态资源也应该从同一域名提供;使用与主页面 HTTP 相同的域名,消除了额外的 DNS 查询以及(潜在的)socket 连接,它们都会减慢静态资源的获取。把阻塞渲染的资源放在同样的域名下,也可以提升性能。6.7.4 资源预取资源预取也是一项 Web 性能优化手段,它提示浏览器只要有可能就继续下载可缓存资源,并把这些资源缓存起来。尽管如此,如果浏览器很忙,或者资源下载花的时间太 长,预取请求将会被忽略。资源预取可以在 HTML 中插入 link 标签实现:<link rel=“prefetch” href="/important.css”>也可以使用 HTTP 响应中的 Link 首部: Link: </important.css>; rel=prefetch 资源预取与 h2 引入的服务端推送并没多少关联。服务端推送用于让资源更快到达浏览器, 而资源预取相比推送的优点之一是,如果资源已经在缓存里,浏览器就不会浪费时间和带宽重复请求它。所以,可以把它看作 h2 推送的补充工具,而不是将被替代的特性。6.8 现实情况中的性能网络丢包是 h2 的命门,一次丢包机会就会让它的所有优化泡汤。7. HTTP/2 实现7.1 桌面Web浏览器所有浏览器在进行 HTTP/2 传输时都需要使用 TLS(HTTPS),即使事实上HTTP/2 规范本身并没有强制要求 TLS。这个原因是:从之前对 WebSocket 和 SPDY 的实验看来,使用 Upgrade 首部,通过 80 端口(明文的 HTTP 端口)通信时,通信链路上代理服务器的中断等因素会导致非常高的错误率。如果基于 443 端口(HTTPS 端口)上的 TLS 发起请求,错误率会显著降低,并且协议通信也更简洁。人们越来越相信,考虑到安全和隐私,一切都应该被加密。HTTP/2 被视为一次推动全网加密通信发展的机会。7.1.2 禁用HTTP/2HTTP/2 毕竟是新鲜事物,现在很多浏览器都支持启用或禁用 h2。7.1.3 支持 HTTP/2 服务端推送服务端推送是 h2 中最令人兴奋也最难正确使用的特性之一,现在所有的主流浏览器都已经支持了此特性。7.1.4 连接归并如果需要建立一个新连接,而浏览器支持连接归并,那么通过复用之前已经存在的连接,就能够提升请求性能。这意味着可以跳过 TCP 和 TLS 的握手过程,改善首次请求新域名的性能。如果浏览器支持连接归并,它会在开启新连接之前先检查是否已经建立了到相同目的地的连接。相同目的地具体指的是:已经存在连接,其证书对新域名有效,此域名可以被解析成那个连接对应的 IP 地址。如果上述条件都满足,那么浏览器会在已建立的连接上向该域名发起 HTTP/2 请求。7.2 服务器、代理以及缓存如果要通过 h2 传输内容,我们有几个选择。支持 HTTP/2 的网络设施大致有以下两类。 Web服务器 :通常所说的提供静态和动态内容服务的程序。 代理/缓存 :一般处在服务器和最终用户之间,可以提供缓存以减轻服务器负载,或进行额外加工。许多代理也能扮演 Web 服务器的角色。 在选择 HTTP/2 服务器时,我们需要检查、评估一些关键点。除了基本的通用性能、操作系统支持、学习曲线、可扩展性以及稳定性,还应当关注 Web 请求的依赖项和优先级,以及对服务端推送的支持。7.3 内容分发网络 CDN内容分发网络(CDN)是反向代理服务器的全球性分布式网络,它部署在多个数据中心。CDN 的目标是通过缩短与最终用户的距离来减少请求往返次数,以此为最终用户提供高可用、高性能的内容服务。大多数主流 CDN 是支持 HTTP/2 的,选择 CDN 时主要考虑的两点是:对服务端推送的支持,以及它们处理优先级的方式。这两点对现实世界中的 Web 性能影响重大。8. HTTP/2调试8.1 chrome devtools 可视化Chrome 开发者工具中的 Network 栏,有助于简单直观地跟踪客户端和服务端的通讯,它 按下面表格的形式展示了若干信息:资源名资源大小状态码优先级总加载时间使用时间线方式分解加载时间打开 devtools 的 Network 栏,鼠标放在瀑布流 Waterfall 的资源上,就会看到资源加载过程中各个阶段的详细时间Connection Setup (连接设置)Queueing :请求被渲染引擎或者网络层延迟的时间,浏览器在以下情况下对请求排队存在更高优先级的请求。此源已打开六个 TCP 连接,达到限值。 仅适用于 HTTP/1.0 和 HTTP/1.1浏览器正在短暂分配磁盘缓存中的空间Connection Start (开始连接阶段)Stalled :请求可能会因 Queueing 中描述的任何原因而停止Proxy negotiation :浏览器与代理服务器协商请求花费的时间DNS Lookup :浏览器解析请求的 IP 地址花费的时间Request/Response (请求 / 响应)Request Sent :发送请求包含的数据花费的时间Waiting (TTFB) :等待初始响应花费的时间,也就是所说的首字节时间;这个数字包括等待服务器传输响应的时间,以及往返服务器的延迟Content Download :接收响应的数据所花费的时间Explanation (总时间)其他ServiceWorker Preparation :浏览器正在启动 Service WorkerRequest to ServiceWorker :正在将请求发送到 Service WorkerReceiving Push :浏览器正在通过 HTTP/2 服务器推送接收此响应的数据Reading Push :浏览器正在读取之前收到的本地数据9. 展望未来HTTP/2 的弱点之一就是依赖主流 TCP 实现。在 3.1.3 节中已经讨论过,TCP 连接受制于 TCP 慢启动、拥塞规避,以及不合理的丢包处理机制。用单个链接承载页面涉及的所有资源请求,就能享受多路复用带来的好处;然而面对 TCP 层级的队首阻塞时,我们还是束手无策。所以 Google 开发的 QUIC 采纳了 HTTP/2 的优点,并且避免了这些缺点。推介阅读:HTTP2 详解 | Wangriyu’s BlogPS:欢迎大家关注我的公众号【前端下午茶】,一起加油吧 ...

April 16, 2019 · 6 min · jiezi

前端应该知道的http

作为互联网通信协议的一员老将,HTTP 协议走到今天已经经历了三次版本的变动,现在最新的版本是 HTTP2.0,相信大家早已耳熟能详。今天就给大家好好介绍一下 HTTP 的前世今生。1、http的历史简介先简单的介绍一下,后面再具体详解1.1、HTTP/0.9HTTP 的最早版本诞生在 1991 年,这个最早版本和现在比起来极其简单,没有 HTTP 头,没有状态码,甚至版本号也没有,后来它的版本号才被定为 0.9 来和其他版本的 HTTP 区分。HTTP/0.9 只支持一种方法—— Get,请求只有一行。GET /hello.html响应也是非常简单的,只包含 html 文档本身。<HTML>Hello world</HTML>当 TCP 建立连接之后,服务器向客户端返回 HTML 格式的字符串。发送完毕后,就关闭 TCP 连接。由于没有状态码和错误代码,如果服务器处理的时候发生错误,只会传回一个特殊的包含问题描述信息的 HTML 文件。这就是最早的 HTTP/0.9 版本。1.2、HTTP/1.01996 年,HTTP/1.0 版本发布,大大丰富了 HTTP 的传输内容,除了文字,还可以发送图片、视频等,这为互联网的发展奠定了基础。相比 HTTP/0.9,HTTP/1.0 主要有如下特性:请求与响应支持 HTTP 头,增加了状态码,响应对象的一开始是一个响应状态行协议版本信息需要随着请求一起发送,支持 HEAD,POST 方法支持传输 HTML 文件以外其他类型的内容 一个典型的 HTTP/1.0 的请求像这样:GET /hello.html HTTP/1.0User-Agent:NCSA_Mosaic/2.0(Windows3.1)200 OKDate: Tue, 15 Nov 1996 08:12:31 GMTServer: CERN/3.0 libwww/2.17Content-Type: text/html<HTML>一个包含图片的页面<IMGSRC="/smile.gif"></HTML>1.3、HTTP/1.1在 HTTP/1.0 发布几个月后,HTTP/1.1 就发布了。HTTP/1.1 更多的是作为对 HTTP/1.0 的完善,在 HTTP1.1 中,主要具有如下改进:可以复用连接增加 pipelinechunked 编码传输引入更多缓存控制机制引入内容协商机制请求消息和响应消息都支持 Host 头域新增了 OPTIONS,PUT, DELETE, TRACE, CONNECT 方法1.4、 HTTPSHTTPS 是以安全为目标的 HTTP 通道,简单讲是 HTTP 的安全版,即 HTTP 下加入 SSL 层,HTTPS 的安全基础是 SSL,因此加密的详细内容就需要 SSL。HTTPS 协议的主要作用可以分为两种:一种是建立一个信息安全通道,来保证数据传输的安全;另一种就是确认网站的真实性。 HTTPS 和 HTTP 的区别主要如下:HTTPS 协议使用 ca 申请证书,由于免费证书较少,需要一定费用。HTTP 是明文传输,HTTPS 则是具有安全性的 SSL 加密传输协议。HTTP 和 HTTPS使用的是完全不同的连接方式,用的端口也不一样,前者是 80,后者是 443。1.5、SPDY在 2010 年到 2015 年,谷歌通过实践一个实验性的 SPDY 协议,证明了一个在客户端和服务器端交换数据的另类方式。其收集了浏览器和服务器端的开发者的焦点问题,明确了响应数量的增加和解决复杂的数据传输。在启动 SPDY 这个项目时预设的目标是:页面加载时间 (PLT) 减少 50%。无需网站作者修改任何内容。将部署复杂性降至最低,无需变更网络基础设施。与开源社区合作开发这个新协议。收集真实性能数据,验证这个实验性协议是否有效。为了达到降低目标,减少页面加载时间的目标,SPDY 引入了一个新的二进制分帧数据层,以实现多向请求和响应、优先次序、最小化及消除不必要的网络延迟,目的是更有效地利用底层 TCP 连接。1.6、 HTTP/2.0时间来到 2015 年,HTTP/2.0 问世。先来介绍一下 HTTP/2.0 的特点吧:使用二进制分帧层多路复用数据流优先级服务端推送头部压缩2、http原理详解HTTP协议是构建在TCP/IP协议之上的,是TCP/IP协议的一个子集,所以要理解HTTP协议,有必要先了解下TCP/IP协议相关的知识。2.1 TCP/IP协议TCP/IP协议族是由一个四层协议组成的系统,这四层分别为:应用层、传输层、网络层和数据链路层分层的好处是把各个相对独立的功能解耦,层与层之间通过规定好的接口来通信。如果以后需要修改或者重写某一个层的实现,只要接口保持不变也不会影响到其他层的功能。接下来,我们将会介绍各个层的主要作用。1) 应用层应用层一般是我们编写的应用程序,其决定了向用户提供的应用服务。应用层可以通过系统调用与传输层进行通信。处于应用层的协议非常多,比如:FTP(File Transfer Protocol,文件传输协议)、DNS(Domain Name System,域名系统)和我们本章讨论的HTTP(HyperText Transfer Protocol,超文本传输协议)等。2) 传输层传输层通过系统调用向应用层提供处于网络连接中的两台计算机之间的数据传输功能。在传输层有两个性质不同的协议:TCP(Transmission Control Protocol,传输控制协议)和UDP(User Data Protocol,用户数据报协议)。3) 网络层网络层用来处理在网络上流动的数据包,数据包是网络传输的最小数据单位。该层规定了通过怎样的路径(传输路线)到达对方计算机,并把数据包传输给对方。IP协议4) 链路层链路层用来处理连接网络的硬件部分,包括控制操作系统、硬件设备驱动、NIC(Network Interface Card,网络适配器)以及光纤等物理可见部分。硬件上的范畴均在链路层的作用范围之内。数据包封装上层协议数据是如何转变为下层协议数据的呢?这是通过封装(encapsulate)来实现的。应用程序数据在发送到物理网络之前,会沿着协议栈从上往下传递。每层协议都将在上层协议数据的基础上加上自己的头部信息(链路层还会加上尾部信息),以为实现该层功能提供必要的信息.发送端发送数据时,数据会从上层传输到下层,且每经过一层都会被打上该层的头部信息。而接收端接收数据时,数据会从下层传输到上层,传输前会把下层的头部信息删除.由于下层协议的头部信息对上层协议是没有实际的用途,所以在下层协议传输数据给上层协议的时候会把该层的头部信息去掉,这个封装过程对于上层协议来说是完全透明的。这样做的好处是,应用层只需要关心应用服务的实现,而不用管底层的实现。TCP三次握手从上面的介绍可知,传输层协议主要有两个:TCP协议和UDP协议。TCP协议相对于UDP协议的特点是:TCP协议提供面向连接、字节流和可靠的传输。第一次握手:客户端发送带有SYN标志的连接请求报文段,然后进入SYN_SEND状态,等待服务端的确认。第二次握手:服务端接收到客户端的SYN报文段后,需要发送ACK信息对这个SYN报文段进行确认。同时,还要发送自己的SYN请求信息。服务端会将上述的信息放到一个报文段(SYN+ACK报文段)中,一并发送给客户端,此时服务端将会进入SYN_RECV状态。第三次握手:客户端接收到服务端的SYN+ACK报文段后,会想服务端发送ACK确认报文段,这个报文段发送完毕后,客户端和服务端都进入ESTABLISHED状态,完成TCP三次握手。当三次握手完成后,TCP协议会为连接双方维持连接状态。为了保证数据传输成功,接收端在接收到数据包后必须发送ACK报文作为确认。如果在指定的时间内(这个时间称为重新发送超时时间),发送端没有接收到接收端的ACK报文,那么就会重发超时的数据。2.2、 DNS 域名解析当你在浏览器的地址栏输入 https://juejin.im 后会发生什么,大家在心中肯定是有一个大概的,这里我将 DNS 域名解析 这个步骤详细的讲一遍。在讲概念之前我先放上一张经典的图文供大家思考一分钟。查找域名对应的 IP 地址的具体过程浏览器搜索自己的 DNS 缓存(浏览器维护一张域名与 IP 地址的对应表);如果没有命中,进入下一步;搜索操作系统中的 DNS 缓存;如果没有命中,进入下一步;搜索操作系统的 hosts 文件( Windows 环境下,维护一张域名与 IP 地址的对应表);如果没有命中,进入下一步;列表项目操作系统将域名发送至 LDNS (本地区域名服务器),LDNS 查询自己的 DNS 缓存(一般命中率在 80% 左右),查找成功则返回结果,失败则发起一个迭代 DNS 解析请求:LDNS向 Root Name Server(根域名服务器,如com、net、im 等的顶级域名服务器的地址)发起请求,此处,Root Name Server 返回 im 域的顶级域名服务器的地址;LDNS 向 im 域的顶级域名服务器发起请求,返回 juejin.im 域名服务器地址;LDNS 向 juejin.im 域名服务器发起请求,得到 juejin.im 的 IP 地址;LDNS 将得到的 IP 地址返回给操作系统,同时自己也将 IP 地址缓存起来;操作系统将 IP 地址返回给浏览器,同时自己也将 IP 地址缓存起来。http工作的简单过程地址解析: 这一步比较重要的是上面的DNS解析封装HTTP请求数据包: 把以上部分结合本机自己的信息,封装成一个HTTP请求数据包封装成TCP包,建立TCP连接(TCP的三次握手)客户机发送请求命令服务器响应服务器关闭TCP连接2.3、http请求方法一些常见的http请求方法。GET: 用于获取数据POST: 用于将实体提交到指定的资源,通常导致状态或服务器上的副作用的更改HEAD: 与GET请求的响应相同的响应,但没有响应体PUT: 用于创建或更新指定资源DELETE: 删除指定的资源关于get与post的一些区别。可以看我的另一篇文章面试经典之http中get与post的区别2.4、 http缓存http很重要的一点还有他的缓存机制。关于这部分的内容可以看一下我之前的文章浏览器缓存看这一篇就够了。这里就不在赘述了。2.5、状态码这里主要讲一些常用的状态码1、 301 永久转移当你想换域名的时候,就可以使用301,如之前的域名叫www.renfed.com,后来换了一个新域名fed.renren.com,希望用户访问老域名的时候能够自动跳转到新的域名,那么就可以使用nginx返回301:server { listen 80; server_name www.renfed.com; root /home/fed/wordpress; return 301 https://fed.renren.com$request_uri;}浏览器收到301之后,就会自动跳转了。搜索引擎在爬的时候如果发现是301,在若干天之后它会把之前收录的网页的域名给换了。还有一个场景,如果希望访问http的时候自动跳转到https也是可以用301,因为如果直接在浏览器地址栏输入域名然后按回车,前面没有带https,那么是默认的http协议,这个时候我们希望用户能够访问安全的https的,不要访问http的,所以要做一个重定向,也可以使用301,如:server { listen 80; server_name fed.renren.com; if ($scheme != “https”) { return 301 https://$host$request_uri; } }2、302 Found 资源暂时转移很多短链接跳转长链接就是使用的302,如下图所示:3、304 Not Modified 没有修改这个主要在上面的缓存哪里出现的比较多。如果服务器没有修改。就会使用浏览器的缓存。4、400 Bad Request 请求无效当必要参数缺失、参数格式不对时,后端通常会返回400,如下图所示:5、403 Forbidden 拒绝服务服务能够理解你的请求,包括传参正确,但是拒绝提供服务。例如,服务允许直接访问静态文件,但是不允许访问某个目录:否则,别人对你服务器上的文件就一览无遗了。403和401的区别在于,401是没有认证,没有登陆验证之类的错误。6、500 内部服务器错误如业务代码出现了异常没有捕获,被tomcat捕获了,就会返回500错误:如:数据库字段长度限制为30个字符,如果没有判断直接插入一条31个字符的记录,就会导致数据库抛异常,如果异常没有捕获处理,就直接返回500。当服务彻底挂了,连返回都没有的时候,那么就是502了。7、502 Bad Gateway 网关错误这种情况是因为nginx收到请求,但是请求没有打过去,可能是因为业务服务挂了,或者是打过去的端口号写错了8、504 Gateway Timeout 网关超时通常是因为服务处理请求太久,导致超时,如PHP服务默认的请求响应最长处理时间为30s,如果超过30s,将会挂掉,返回504,如下图所示:2.6、HTTP的基本优化影响一个HTTP网络请求的因素主要有两个:带宽和延迟。带宽如果说我们还停留在拨号上网的阶段,带宽可能会成为一个比较严重影响请求的问题,但是现在网络基础建设已经使得带宽得到极大的提升,我们不再会担心由带宽而影响网速,那么就只剩下延迟了。延迟1、浏览器阻塞(HOL blocking):浏览器会因为一些原因阻塞请求。浏览器对于同一个域名,同时只能有 4 个连接(这个根据浏览器内核不同可能会有所差异),超过浏览器最大连接数限制,后续请求就会被阻塞。2、DNS 查询(DNS Lookup):浏览器需要知道目标服务器的 IP 才能建立连接。将域名解析为 IP 的这个系统就是 DNS。这个通常可以利用DNS缓存结果来达到减少这个时间的目的。3、建立连接(Initial connection):HTTP 是基于 TCP 协议的,浏览器最快也要在第三次握手时才能捎带 HTTP 请求报文,达到真正的建立连接,但是这些连接无法复用会导致每次请求都经历三次握手和慢启动。三次握手在高延迟的场景下影响较明显,慢启动则对文件类大请求影响较大http的发展也就是在不断地优化这些方向上的问题。3、http1.1HTTP1.0最早在网页中使用是在1996年,那个时候只是使用一些较为简单的网页上和网络请求上,而HTTP1.1则在1999年才开始广泛应用于现在的各大浏览器网络请求中,同时HTTP1.1也是当前使用最为广泛的HTTP协议。 主要区别主要体现在:缓存处理,在HTTP1.0中主要使用header里的If-Modified-Since,Expires来做为缓存判断的标准,HTTP1.1则引入了更多的缓存控制策略例如Entity tag,If-Unmodified-Since, If-Match, If-None-Match等更多可供选择的缓存头来控制缓存策略。带宽优化及网络连接的使用,HTTP1.0中,存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了,并且不支持断点续传功能,HTTP1.1则在请求头引入了range头域,它允许只请求资源的某个部分,即返回码是206(Partial Content),这样就方便了开发者自由的选择以便于充分利用带宽和连接。错误通知的管理,在HTTP1.1中新增了24个错误状态响应码,如409(Conflict)表示请求的资源与资源的当前状态发生冲突;410(Gone)表示服务器上的某个资源被永久性的删除。Host头处理,在HTTP1.0中认为每台服务器都绑定一个唯一的IP地址,因此,请求消息中的URL并没有传递主机名(hostname)。但随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机(Multi-homed Web Servers),并且它们共享一个IP地址。HTTP1.1的请求消息和响应消息都应支持Host头域,且请求消息中如果没有Host头域会报告一个错误(400 Bad Request)。长连接,HTTP 1.1支持长连接(PersistentConnection)和请求的流水线(Pipelining)处理,在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟,在HTTP1.1中默认开启Connection: keep-alive,一定程度上弥补了HTTP1.0每次请求都要创建连接的缺点。虽然 HTTP/1.1 已经优化了很多点,作为一个目前使用最广泛的协议版本,已经能够满足很多网络需求,但是随着网页变得越来越复杂,甚至演变成为独立的应用,HTTP/1.1 逐渐暴露出了一些问题:在传输数据时,每次都要重新建立连接,对移动端特别不友好传输内容是明文,不够安全header 内容过大,每次请求 header 变化不大,造成浪费keep-alive 给服务端带来性能压力 为了解决这些问题,HTTPS 和 SPDY 应运而生。4、HTTPSHTTPS 是以安全为目标的 HTTP 通道,简单讲是 HTTP 的安全版,即 HTTP 下加入 SSL 层,HTTPS 的安全基础是 SSL,因此加密的详细内容就需要 SSL。HTTPS协议需要到CA申请证书,一般免费证书很少,需要交费。HTTP协议运行在TCP之上,所有传输的内容都是明文,HTTPS运行在SSL/TLS之上,SSL/TLS运行在TCP之上,所有传输的内容都经过加密的。HTTP和HTTPS使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。HTTPS可以有效的防止运营商劫持,解决了防劫持的一个大问题。5、SPDY:HTTP1.x的优化2012年google如一声惊雷提出了SPDY的方案,优化了HTTP1.X的请求延迟,解决了HTTP1.X的安全性,具体如下:降低延迟,针对HTTP高延迟的问题,SPDY优雅的采取了多路复用(multiplexing)。多路复用通过多个请求stream共享一个tcp连接的方式,解决了HOL blocking的问题,降低了延迟同时提高了带宽的利用率。请求优先级(request prioritization)。多路复用带来一个新的问题是,在连接共享的基础之上有可能会导致关键请求被阻塞。SPDY允许给每个request设置优先级,这样重要的请求就会优先得到响应。比如浏览器加载首页,首页的html内容应该优先展示,之后才是各种静态资源文件,脚本文件等加载,这样可以保证用户能第一时间看到网页内容。header压缩。前面提到HTTP1.x的header很多时候都是重复多余的。选择合适的压缩算法可以减小包的大小和数量。基于HTTPS的加密协议传输,大大提高了传输数据的可靠性。服务端推送(server push),采用了SPDY的网页,例如我的网页有一个sytle.css的请求,在客户端收到sytle.css数据的同时,服务端会将sytle.js的文件推送给客户端,当客户端再次尝试获取sytle.js时就可以直接从缓存中获取到,不用再发请求了。SPDY构成图:SPDY位于HTTP之下,TCP和SSL之上,这样可以轻松兼容老版本的HTTP协议(将HTTP1.x的内容封装成一种新的frame格式),同时可以使用已有的SSL功能。6、HTTP2.0HTTP2.0可以说是SPDY的升级版(其实原本也是基于SPDY设计的),但是,HTTP2.0 跟 SPDY 仍有不同的地方,如下:HTTP2.0和SPDY的区别:HTTP2.0 支持明文 HTTP 传输,而 SPDY 强制使用 HTTPSHTTP2.0 消息头的压缩算法采用 HPACK,而非 SPDY 采用的 DEFLATEHTTP/2 新特性6.1、二进制传输HTTP/2 采用二进制格式传输数据,而非 HTTP 1.x 的文本格式,二进制协议解析起来更高效。 HTTP / 1 的请求和响应报文,都是由起始行,首部和实体正文(可选)组成,各部分之间以文本换行符分隔。HTTP/2 将请求和响应数据分割为更小的帧,并且它们采用二进制编码。接下来我们介绍几个重要的概念:流:流是连接中的一个虚拟信道,可以承载双向的消息;每个流都有一个唯一的整数标识符(1、2…N);消息:是指逻辑上的 HTTP 消息,比如请求、响应等,由一或多个帧组成。帧:HTTP 2.0 通信的最小单位,每个帧包含帧首部,至少也会标识出当前帧所属的流,承载着特定类型的数据,如 HTTP 首部、负荷,等等HTTP/2 中,同域名下所有通信都在单个连接上完成,该连接可以承载任意数量的双向数据流。每个数据流都以消息的形式发送,而消息又由一个或多个帧组成。多个帧之间可以乱序发送,根据帧首部的流标识可以重新组装。6.2、多路复用在 HTTP/2 中引入了多路复用的技术。多路复用很好的解决了浏览器限制同一个域名下的请求数量的问题,同时也接更容易实现全速传输,毕竟新开一个 TCP 连接都需要慢慢提升传输速度。在 HTTP/2 中,有了二进制分帧之后,HTTP /2 不再依赖 TCP 链接去实现多流并行了,在 HTTP/2中:同域名下所有通信都在单个连接上完成。单个连接可以承载任意数量的双向数据流。数据流以消息的形式发送,而消息又由一个或多个帧组成,多个帧之间可以乱序发送,因为根据帧首部的流标识可以重新组装。这一特性,使性能有了极大提升:同个域名只需要占用一个 TCP 连接,使用一个连接并行发送多个请求和响应,消除了因多个 TCP 连接而带来的延时和内存消耗。并行交错地发送多个请求,请求之间互不影响。并行交错地发送多个响应,响应之间互不干扰。在HTTP/2中,每个请求都可以带一个31bit的优先值,0表示最高优先级, 数值越大优先级越低。有了这个优先值,客户端和服务器就可以在处理不同的流时采取不同的策略,以最优的方式发送流、消息和帧。如上图所示,多路复用的技术可以只通过一个 TCP 连接就可以传输所有的请求数据。6.3、Header 压缩在 HTTP/1 中,我们使用文本的形式传输 header,在 header 携带 cookie 的情况下,可能每次都需要重复传输几百到几千的字节。为了减少这块的资源消耗并提升性能, HTTP/2对这些首部采取了压缩策略:HTTP/2在客户端和服务器端使用“首部表”来跟踪和存储之前发送的键-值对,对于相同的数据,不再通过每次请求和响应发送;首部表在HTTP/2的连接存续期内始终存在,由客户端和服务器共同渐进地更新;每个新的首部键-值对要么被追加到当前表的末尾,要么替换表中之前的值例如下图中的两个请求, 请求一发送了所有的头部字段,第二个请求则只需要发送差异数据,这样可以减少冗余数据,降低开销6.4、服务端推送(Server Push)Server Push即服务端能通过push的方式将客户端需要的内容预先推送过去,也叫“cache push”。可以想象以下情况,某些资源客户端是一定会请求的,这时就可以采取服务端 push 的技术,提前给客户端推送必要的资源,这样就可以相对减少一点延迟时间。当然在浏览器兼容的情况下你也可以使用 prefetch。例如服务端可以主动把JS和CSS文件推送给客户端,而不需要客户端解析HTML时再发送这些请求。服务端可以主动推送,客户端也有权利选择是否接收。如果服务端推送的资源已经被浏览器缓存过,浏览器可以通过发送RST_STREAM帧来拒收。主动推送也遵守同源策略,换句话说,服务器不能随便将第三方资源推送给客户端,而必须是经过双方确认才行。后续更多文章将在我的github第一时间发布,欢迎关注。参考HTTP协议详解前端词典】进阶必备的网络基础我知道的HTTP请求一文读懂HTTP/2 及 HTTP/3特性 ...

April 12, 2019 · 2 min · jiezi

从理论到实践 全面理解HTTP/2

前言为了降低加载时间,相信大多数人都做过如下尝试Keep-alive: TCP持久连接,增加了TCP连接的复用性,但只有当上一个请求/响应完全完成后,client才能发送下一个请求Pipelining: 可同时发送多个请求,但是服务器必须严格按照请求的先后顺序返回响应,若第一个请求的响应迟迟不能返回,那后面的响应都会被阻塞,也就是所谓的队头阻塞请求合并:雪碧图,css/js内联、css/js合并等,然而请求合并又会带来缓存失效、解析变慢、阻塞渲染、木桶效应等诸多问题域名散列:绕过了同域名最多6个TCP的限制,但增加了DNS开销和TCP开销,也会大幅降低缓存的利用率……不可否认,这些优化在一定程度上降低了网站加载时间,但对于一个web应用庞大的请求量来说,这些只是冰上一角、隔靴搔痒。以上问题归根结底是HTTP1.1协议本身的问题,若要从根本上解决HTTP1.1的低效,只能从协议本身入手。为此Google开发了SPDY协议,主要是为了降低传输时间;基于SPDY协议,IETF和SPDY组全体成员共同开发了HTTP/2,并在2015年5月以RFC 7504正式发表。SPDY或者HTTP/2并不是一个全新的协议,它只是修改了HTTP的请求与应答在网络上的传输方式,增加了一个spdy传输层,用于处理、标记、简化和压缩HTTP请求,所以它们并不会破坏现有程序的工作,对于支持的场景,使用新特性可以获得更快的速度,对于不支持的场景,也可以实现平稳退化。HTTP/2继承了spdy的多路复用、优先级排序等诸多优秀特性,也额外做了不少改进。其中较为显著的改进是HTTP/2使用了一份经过定制的压缩算法,以此替代了SPDY的动态流压缩算法,用于避免对协议的Oracle攻击。多数主流浏览器已在2015年底支持了该标准(划重点)。具体支持度如下:数据来源可以看到国内有58.55%的浏览器已经完全支持HTTP/2,而全球的支持度更是高达85.66%。这么高的支持度,so,你心动了吗why HTTP/2二进制格式传输我们知道HTTP/1.1的头信息肯定是文本(ASCII编码),数据体可以是文本,也可以是二进制(需要做自己做额外的转换,协议本身并不会转换)。而在HTTP/2中,新增了二进制分帧层,将数据转换成二进制,也就是说HTTP/2中所有的内容都是采用二进制传输。使用二进制有什么好处吗?当然!效率会更高,而且最主要的是可以定义额外的帧,如果用文本实现帧传输,解析起来将会十分麻烦。HTTP/2共定义了十种帧,较为常见的有数据帧、头部帧、PING帧、SETTING帧、优先级帧和PUSH_PROMISE帧等,为将来的高级应用打好了基础。如上图,Binary Framing就是新增的二进制分帧层。多路复用二进制分帧层把数据转换为二进制的同时,也把数据分成了一个一个的帧。帧是HTTP/2中数据传输的最小单位;每个帧都有stream_ID字段,表示这个帧属于哪个流,接收方把stream_ID相同的所有帧组合到一起就是被传输的内容了。而流是HTTP/2中的一个逻辑上的概念,它代表着HTTP/1.1中的一个请求或者一个响应,协议规定client发给server的流的stream_ID为奇数,server发给client的流ID是偶数。需要注意的是,流只是一个逻辑概念,便于理解和记忆的,实际并不存在。理解了帧和流的概念,完整的HTTP/2的通信就可以被形象地表示为这样:可以发现,在一个TCP链接中,可以同时双向地发送帧,而且不同流中的帧可以交错发送,不需要等某个流发送完,才发送下一个。也就是说在一个TCP连接中,可以同时传输多个流,即可以同时传输多个HTTP请求和响应,这种同时传输不需要遵循先入先出等规定,因此也不会产生阻塞,效率极高。在这种传输模式下,HTTP请求变得十分廉价,我们不需要再时刻顾虑网站的http请求数是否太多、TCP连接数是否太多、是否会产生阻塞等问题了。HPACK 首部压缩为什么需要压缩?在 HTTP/1 中,HTTP 请求和响应都是由「状态行、请求 / 响应头部、消息主体」三部分组成。一般而言,消息主体都会经过 gzip 压缩,或者本身传输的就是压缩过后的二进制文件(例如图片、音频),但状态行和头部却没有经过任何压缩,直接以纯文本传输。随着 Web 功能越来越复杂,每个页面产生的请求数也越来越多,根据 HTTP Archive 的统计,当前平均每个页面都会产生上百个请求。越来越多的请求导致消耗在头部的流量越来越多,尤其是每次都要传输 UserAgent、Cookie 这类不会频繁变动的内容,完全是一种浪费。为了减少冗余的头部信息带来的消耗,HTTP/2采用HPACK 算法压缩请求和响应的header。下面这张图非常直观地表达了HPACK头部压缩的原理:图片来源: Velocity 2015 • SC 会议分享具体规则可以描述为:通信双方共同维护了一份静态表,包含了常见的头部名称与值的组合根据先入先出的原则,维护一份可动态添加内容的动态表用基于该静态哈夫曼码表的哈夫曼编码数据当要发送一个请求时,会先将其头部和静态表对照,对于完全匹配的键值对,可以直接使用一个数字表示,如上图中的2:method: GET,对于头部名称匹配的键值对,可以将名称使用一个数字传输,如上图中的19:path: /resource,同时告诉服务端将它添加到动态表中,以后的相同键值对就用一个数字表示了。这样,像cookie这些不经常变动的值,只用发送一次就好了。server push在开始HTTP/2 server push 前,我们先来看看一个HTTP/1.1的页面是如何加载的。<!DOCTYPE html><html><head> <link rel=“stylesheet” href=“style.css”> <script src=“user.js”></script></head><body> <h1>hello http2</h1></body></html>浏览器向服务器请求/user.html服务器处理请求,把/user.html发给浏览器浏览器解析收到的/user.html,发现还需要请求/user.js和style.css静态资源分别发送两个请求,获取/user.js和style.css服务器分别响应两个请求,发送资源浏览器收到资源,渲染页面至此,这个页面才加载完毕,可以被用户看到。可以发现在步骤3和4中,服务器一直处于空闲等待状态,而浏览器到第6步才能得到资源渲染页面,这使页面的首次加载变得缓慢。而HTTP/2的server push允许服务器在未收到请求时就向浏览器推送资源。即服务器发送/user.html时,就可以主动把/user.js和style.csspush给浏览器,使资源提前达到浏览器;除了静态文件,还可以推送比较耗时的API,只是需要提前将参数和cookie等信息通过某个方式告知服务端(如和路由关联)。Apache、GO的net/http、node-spdy都实现了server push(但ngnix没有=_=),本文后面的实践部分用node-spdy写了一个极为简陋的例子,有兴趣的小伙伴可以动手尝试一下。Server push是HTTP/2协议里面唯一一个需要开发者自己配置的功能。其他功能都是服务器和浏览器自动实现,无需开发者介入。在HTTP1.1时代,也有提前获取资源的方法,如preload和prefetch,前者是在页面解析初期就告诉浏览器,这个资源是浏览器马上要用到的,可以立刻发送对资源的请求,当需要用到该资源时就可以直接用而不用等待请求和响应的返回了;后者是当前页面用不到但下一页面可能会用到的资源,优先级较低,只有当浏览器空闲时才会请求prefetch标记的资源。从应用层面上看,preload和server push并没有什么区别,但是server push减少浏览器请求的时间,略优于preload,在一些场景中,可以将两者结合使用。实战纸上谈兵终觉浅,来实践一下吧!亲手搭建自己的 HTTP/2 demo,并抓包验证。spdy这个库实现了 HTTP/2,同时也提供了对express的支持,所以这里我选用spdy + express搭建demo。demo源码路径说明:- ca/ 证书、秘钥等文件- src/ - img/ - js/ - page1.html- server.jsHTTPS 秘钥和证书虽然HTTP/2有加密(h2)和非加密(h2c)两种形式,但大多主流浏览器只支持h2-基于TLS/1.2或以上版本的加密连接,所以在搭建demo前,我们首先要自颁发一个证书,这样就可以在浏览器访问中使用https了,你可以自行搜索证书颁发方法,也可以按照下述步骤去生成首先要安装open-ssl,然后执行以下命令$ openssl genrsa -des3 -passout pass:x -out server.pass.key 2048….$ openssl rsa -passin pass:x -in server.pass.key -out server.keywriting RSA key$ rm server.pass.key$ openssl x509 -req -sha256 -days 365 -in server.csr -signkey server.key -out server.crt….$ openssl x509 -req -sha256 -days 365 -in server.csr -signkey server.key -out server.crt然后你就会得到三个文件server.crt, server.csr, server.key,将它们拷贝到ca文件夹中,稍后会用到。搭建HTTP/2服务express是一个Node.js框架,这里我们用它声明了路由/,返回的html文件page1.html中引用了js和图片等静态资源。// server.jsconst http2 = require(‘spdy’)const express = require(’express’)const app = express()const publicPath = ‘src’app.use(express.static(publicPath))app.get(’/’, function (req, res) { res.setHeader(‘Content-Type’, ’text/html’) res.sendFile(__dirname + ‘/src/page1.html’)})var options = { key: fs.readFileSync(’./ca/server.key’), cert: fs.readFileSync(’./ca/server.crt’)}http2.createServer(options, app).listen(8080, () => { console.log(Server is listening on https://127.0.0.1:8080 .)})用浏览器访问https://127.0.0.1:8080/,打开控制台可以看所有的请求和它们的瀑布图:可以清楚地看到,当第一个请求,也就是对document的请求完全返回并解析后,浏览器才开始发起对js和图片等静态资源的的请求。前面说过,server push允许服务器主动向浏览器推送资源,那么是否可以在第一个请求未完成时,就把接下来所需的js和img推送给浏览器呢?这样不仅充分利用了HTTP/2的多路复用,还减少了服务器的空闲等待时间。对路由的处理函数进行改造:app.get(’/’, function (req, res) {+ push(’/img/yunxin1.png’, res, ‘image/png’)+ push(’/img/yunxin2.png’, res, ‘image/png’)+ push(’/js/log3.js’, res, ‘application/javascript’) res.setHeader(‘Content-Type’, ’text/html’) res.sendFile(__dirname + ‘/src/page1.html’)})function push (reqPath, target, type) { let content = fs.readFileSync(path.join(__dirname, publicPath, reqPath)) let stream = target.push(reqPath, { status: 200, method: ‘GET’, request: { accept: ‘/’ }, response: { ‘content-type’: type } }) stream.on(’error’, function() {}) stream.end(content)}来看下应用了server push的瀑布图:很明显,被push的静态资源可以很快地被使用,而没有被push的资源,如log1.js和log2.js则需要经过较长的时间才能被使用。浏览器控制台看到的东西毕竟很有限,我们来玩点更有意思的wireshark 抓包验证wireshark是一款可以识别HTTP/2的抓包工具,它的原理是直接读取并分析网卡数据,我们用它来验证是否真正实现了HTTP/2以及其底层通信原理。首先去官网下载安装包并安装wireshark,这一步没啥好说的。我们知道,http/2里的请求和响应都被拆分成了帧,如果我们直接去抓取HTTP/2通信包,那抓到的只能是一帧一帧地数据,像这样:可以看到,抓到的都是TCP类型的包(红色方框);观察前三个包的内容(绿色方框),分别是SYN、[SYN, ACK]和ACK,这就我们所熟知的TCP三次握手;右下角的黄色小方框是请求当前页面后抓到的TCP包的总数,其实这个页面只有七八个请求,但抓到的包的数量却有334个,这也验证了HTTP/2的请求和响应的确是被分成了一帧一帧的。抓HTTP1.1的包,我们可以清楚地看到都有哪些请求和响应,它们的协议、大小等,而HTTP/2的数据包却是一帧一帧地,那么怎么看HTTP/2都有哪些请求和响应呢?其实wireshark会自动帮我们重组拥有相同stream_ID的帧,重组后就可看到实际有哪些请求和响应了,但是因为我们用的是https,所有的数据都被加密了,wireshark就不知道该怎么去重组了。有两个办法可以在wireshark中解密 HTTPS 流量:第一如果你拥有 HTTPS 网站的加密私钥,可以用加密私钥来解密这个网站的加密流量;2)某些浏览器支持将 TLS 会话中使用的对称密钥保存在外部文件中,可供 Wireshark 解密使用。但是HTTP/2为了前向安全性,不允许使用RAS秘钥交换,所有我们无法使用第一个方法来解密HTTP/2流量。介绍第二种方法:当系统环境变量中存在SSLKEYFILELOG时,Chrome和firefox会将对称秘钥保存在该环境变量指向的文件中,然后把这个文件导入wireshark,就可以解密HTTP/2流量了,具体做法如下:新建ssl.log文件添加系统环境变量SSLKEYFILELOG,指向第一步创建的文件在wireshark中打开 preferences->Protocols,找到SSL,将配置面板的 「(Pre)-Master-Secret log filename」选中第一步创建的文件这时用Chrome或Firefox访问任何一个https页面,ssl.log中应该就有写入的秘钥数据了。解密完成后,我们就可以看到HTTP/2的包了下图是在demo的主页面抓取的包,可以清楚地看到有哪些HTTP/2请求。HTTP/2协议中的流和可以在一个TCP连接中交错传输,只需建立一个TCP连接就可以完成和服务器的所有通信,我们来看下在demo中的HTTP/2是不是这样的:wireshark下方还有一个面板,里面有当前包的具体信息,如大小、源IP、目的IP、端口、数据、协议等,在Transmission Control Protocol下有一个[Stream index],如下图,它是TCP连接的编号,代表当前包是从哪个TCP连接中传输的。观察demo页面请求产生的包,可以发现它们的stream index 都相同,说明这些HTTP/2请求和响应是在一个TCP连接中被传输的,这么多流的确复用了一个TCP连接。除了多路复用外,我们还可以通过抓包来观察HTTP/2的头部压缩。下图是当前路由下的第一个请求,实际被传输的头部数据有253bytes,解压后的头部信息有482bytes。压缩后的大小减少了几乎一半但这只是第一个请求,我们看看后来的请求,如第三个,实际传输的头部大小只有30bytes,而解压后的大小有441byte,压缩后的体积仅为原来的1/14!如今web应用单是一个页面就动辄几百的请求数,HPACK能节约的流量可想而知。结语在文章开篇,我们列举了HTTP1.x时代的困境,引入并简要说明了HTTP/2的起源;然后对比着HTTP1.x,介绍了HTTP/2的诸多优秀特性,来说明为什么选择HTTP/2;在文章的最后一部分,介绍了如何一步一步搭建一个HTTP/2实例,并抓包观察,验证了HTTP/2的多路复用,头部压缩等特性。最后,您是否也被这些高效特性吸引了呢?动手试试吧参考:HTTP/2 维基百科w3c-preloadHTTP/2 Server Push with Node.js使用 Wireshark 调试 HTTP/2 流量Optimize Your App with HTTP/2 Server Push Using Node and Expressweb性能优化与HTTP/2 ...

February 19, 2019 · 2 min · jiezi

从理论到实践,全方位认识HTTP/2

前言为了降低加载时间,相信大多数人都做过如下尝试Keep-alive: TCP持久连接,增加了TCP连接的复用性,但只有当上一个请求/响应完全完成后,client才能发送下一个请求Pipelining: 可同时发送多个请求,但是服务器必须严格按照请求的先后顺序返回响应,若第一个请求的响应迟迟不能返回,那后面的响应都会被阻塞,也就是所谓的队头阻塞请求合并:雪碧图,css/js内联、css/js合并等,然而请求合并又会带来缓存失效、解析变慢、阻塞渲染、木桶效应等诸多问题域名散列:绕过了同域名最多6个TCP的限制,但增加了DNS开销和TCP开销,也会大幅降低缓存的利用率……不可否认,这些优化在一定程度上降低了网站加载时间,但对于一个web应用庞大的请求量来说,这些只是冰上一角、隔靴搔痒。以上问题归根结底是HTTP1.1协议本身的问题,若要从根本上解决HTTP1.1的低效,只能从协议本身入手。为此Google开发了SPDY协议,主要是为了降低传输时间;基于SPDY协议,IETF和SPDY组全体成员共同开发了HTTP/2,并在2015年5月以RFC 7504正式发表。SPDY或者HTTP/2并不是一个全新的协议,它只是修改了HTTP的请求与应答在网络上的传输方式,增加了一个spdy传输层,用于处理、标记、简化和压缩HTTP请求,所以它们并不会破坏现有程序的工作,对于支持的场景,使用新特性可以获得更快的速度,对于不支持的场景,也可以实现平稳退化。HTTP/2继承了spdy的多路复用、优先级排序等诸多优秀特性,也额外做了不少改进。其中较为显著的改进是HTTP/2使用了一份经过定制的压缩算法,以此替代了SPDY的动态流压缩算法,用于避免对协议的Oracle攻击。多数主流浏览器已在2015年底支持了该标准(划重点)。具体支持度如下:数据来源可以看到国内有58.55%的浏览器已经完全支持HTTP/2,而全球的支持度更是高达85.66%。这么高的支持度,so,你心动了吗why HTTP/2二进制格式传输我们知道HTTP/1.1的头信息肯定是文本(ASCII编码),数据体可以是文本,也可以是二进制(需要做自己做额外的转换,协议本身并不会转换)。而在HTTP/2中,新增了二进制分帧层,将数据转换成二进制,也就是说HTTP/2中所有的内容都是采用二进制传输。使用二进制有什么好处吗?当然!效率会更高,而且最主要的是可以定义额外的帧,如果用文本实现帧传输,解析起来将会十分麻烦。HTTP/2共定义了十种帧,较为常见的有数据帧、头部帧、PING帧、SETTING帧、优先级帧和PUSH_PROMISE帧等,为将来的高级应用打好了基础。如上图,Binary Framing就是新增的二进制分帧层。多路复用二进制分帧层把数据转换为二进制的同时,也把数据分成了一个一个的帧。帧是HTTP/2中数据传输的最小单位;每个帧都有stream_ID字段,表示这个帧属于哪个流,接收方把stream_ID相同的所有帧组合到一起就是被传输的内容了。而流是HTTP/2中的一个逻辑上的概念,它代表着HTTP/1.1中的一个请求或者一个响应,协议规定client发给server的流的stream_ID为奇数,server发给client的流ID是偶数。需要注意的是,流只是一个逻辑概念,便于理解和记忆的,实际并不存在。理解了帧和流的概念,完整的HTTP/2的通信就可以被形象地表示为这样:可以发现,在一个TCP链接中,可以同时双向地发送帧,而且不同流中的帧可以交错发送,不需要等某个流发送完,才发送下一个。也就是说在一个TCP连接中,可以同时传输多个流,即可以同时传输多个HTTP请求和响应,这种同时传输不需要遵循先入先出等规定,因此也不会产生阻塞,效率极高。在这种传输模式下,HTTP请求变得十分廉价,我们不需要再时刻顾虑网站的http请求数是否太多、TCP连接数是否太多、是否会产生阻塞等问题了。HPACK 首部压缩为什么需要压缩?在 HTTP/1 中,HTTP 请求和响应都是由「状态行、请求 / 响应头部、消息主体」三部分组成。一般而言,消息主体都会经过 gzip 压缩,或者本身传输的就是压缩过后的二进制文件(例如图片、音频),但状态行和头部却没有经过任何压缩,直接以纯文本传输。随着 Web 功能越来越复杂,每个页面产生的请求数也越来越多,根据 HTTP Archive 的统计,当前平均每个页面都会产生上百个请求。越来越多的请求导致消耗在头部的流量越来越多,尤其是每次都要传输 UserAgent、Cookie 这类不会频繁变动的内容,完全是一种浪费。为了减少冗余的头部信息带来的消耗,HTTP/2采用HPACK 算法压缩请求和响应的header。下面这张图非常直观地表达了HPACK头部压缩的原理:图片来源: Velocity 2015 • SC 会议分享具体规则可以描述为:通信双方共同维护了一份静态表,包含了常见的头部名称与值的组合根据先入先出的原则,维护一份可动态添加内容的动态表用基于该静态哈夫曼码表的哈夫曼编码数据当要发送一个请求时,会先将其头部和静态表对照,对于完全匹配的键值对,可以直接使用一个数字表示,如上图中的2:method: GET,对于头部名称匹配的键值对,可以将名称使用一个数字传输,如上图中的19:path: /resource,同时告诉服务端将它添加到动态表中,以后的相同键值对就用一个数字表示了。这样,像cookie这些不经常变动的值,只用发送一次就好了。server push在开始HTTP/2 server push 前,我们先来看看一个HTTP/1.1的页面是如何加载的。<!DOCTYPE html><html><head> <link rel=“stylesheet” href=“style.css”> <script src=“user.js”></script></head><body> <h1>hello http2</h1></body></html>浏览器向服务器请求/user.html服务器处理请求,把/user.html发给浏览器浏览器解析收到的/user.html,发现还需要请求/user.js和style.css静态资源分别发送两个请求,获取/user.js和style.css服务器分别响应两个请求,发送资源浏览器收到资源,渲染页面至此,这个页面才加载完毕,可以被用户看到。可以发现在步骤3和4中,服务器一直处于空闲等待状态,而浏览器到第6步才能得到资源渲染页面,这使页面的首次加载变得缓慢。而HTTP/2的server push允许服务器在未收到请求时就向浏览器推送资源。即服务器发送/user.html后,就可以主动把/user.js和style.csspush给浏览器,使资源提前达到浏览器。这也是HTTP/2协议里面唯一一个需要开发者自己配置的功能。其他功能都是服务器和浏览器自动实现,无需开发者介入。在HTTP1.1时代,也有提前获取资源的方法,如preload和prefetch,前者是在页面解析初期就告诉浏览器,这个资源是浏览器马上要用到的,可以立刻发送对资源的请求,当需要用到该资源时就可以直接用而不用等待请求和响应的返回了;后者是当前页面用不到但下一页面可能会用到的资源,优先级较低,只有当浏览器空闲时才会请求prefetch标记的资源。从应用层面上看,preload和server push并没有什么区别,但是server push减少浏览器请求的时间,略优于preload,在一些场景中,可以将两者结合使用。实战纸上谈兵终觉浅,来实践一下吧!亲手搭建自己的 HTTP/2 demo,并抓包验证。spdy这个库实现了 HTTP/2,同时也提供了对express的支持,所以这里我选用spdy + express搭建demo。demo源码路径说明:- ca/ 证书、秘钥等文件- src/ - img/ - js/ - page1.html- server.jsHTTPS 秘钥和证书虽然HTTP/2有加密(h2)和非加密(h2c)两种形式,但大多主流浏览器只支持h2-基于TLS/1.2或以上版本的加密连接,所以在搭建demo前,我们首先要自颁发一个证书,这样就可以在浏览器访问中使用https了,你可以自行搜索证书颁发方法,也可以按照下述步骤去生成首先要安装open-ssl,然后执行以下命令$ openssl genrsa -des3 -passout pass:x -out server.pass.key 2048….$ openssl rsa -passin pass:x -in server.pass.key -out server.keywriting RSA key$ rm server.pass.key$ openssl x509 -req -sha256 -days 365 -in server.csr -signkey server.key -out server.crt….$ openssl x509 -req -sha256 -days 365 -in server.csr -signkey server.key -out server.crt然后你就会得到三个文件server.crt, server.csr, server.key,将它们拷贝到ca文件夹中,稍后会用到。搭建HTTP/2服务express是一个Node.js框架,这里我们用它声明了路由/,返回的html文件page1.html中引用了js和图片等静态资源。// server.jsconst http2 = require(‘spdy’)const express = require(’express’)const app = express()const publicPath = ‘src’app.use(express.static(publicPath))app.get(’/’, function (req, res) { res.setHeader(‘Content-Type’, ’text/html’) res.sendFile(__dirname + ‘/src/page1.html’)})var options = { key: fs.readFileSync(’./ca/server.key’), cert: fs.readFileSync(’./ca/server.crt’)}http2.createServer(options, app).listen(8080, () => { console.log(Server is listening on https://127.0.0.1:8080 .)})用浏览器访问https://127.0.0.1:8080/,打开控制台可以看所有的请求和它们的瀑布图:可以清楚地看到,当第一个请求,也就是对document的请求完全返回并解析后,浏览器才开始发起对js和图片等静态资源的的请求。前面说过,server push允许服务器主动向浏览器推送资源,那么是否可以在第一个请求未完成时,就把接下来所需的js和img推送给浏览器呢?这样不仅充分利用了HTTP/2的多路复用,还减少了服务器的空闲等待时间。对路由的处理函数进行改造:app.get(’/’, function (req, res) {+ push(’/img/yunxin1.png’, res, ‘image/png’)+ push(’/img/yunxin2.png’, res, ‘image/png’)+ push(’/js/log3.js’, res, ‘application/javascript’) res.setHeader(‘Content-Type’, ’text/html’) res.sendFile(__dirname + ‘/src/page1.html’)})function push (reqPath, target, type) { let content = fs.readFileSync(path.join(__dirname, publicPath, reqPath)) let stream = target.push(reqPath, { status: 200, method: ‘GET’, request: { accept: ‘/’ }, response: { ‘content-type’: type } }) stream.on(’error’, function() {}) stream.end(content)}来看下应用了server push的瀑布图:很明显,被push的静态资源可以很快地被使用,而没有被push的资源,如log1.js和log2.js则需要经过较长的时间才能被使用。浏览器控制台看到的东西毕竟很有限,我们来玩点更有意思的wireshark 抓包验证wireshark是一款可以识别HTTP/2的抓包工具,它的原理是直接读取并分析网卡数据,我们用它来验证是否真正实现了HTTP/2以及其底层通信原理。首先去官网下载安装包并安装wireshark,这一步没啥好说的。我们知道,http/2里的请求和响应都被拆分成了帧,如果我们直接去抓取HTTP/2通信包,那抓到的只能是一帧一帧地数据,像这样:可以看到,抓到的都是TCP类型的包(红色方框);观察前三个包的内容(绿色方框),分别是SYN、[SYN, ACK]和ACK,这就我们所熟知的TCP三次握手;右下角的黄色小方框是请求当前页面后抓到的TCP包的总数,其实这个页面只有七八个请求,但抓到的包的数量却有334个,这也验证了HTTP/2的请求和响应的确是被分成了一帧一帧的。抓HTTP1.1的包,我们可以清楚地看到都有哪些请求和响应,它们的协议、大小等,而HTTP/2的数据包却是一帧一帧地,那么怎么看HTTP/2都有哪些请求和响应呢?其实wireshark会自动帮我们重组拥有相同stream_ID的帧,重组后就可看到实际有哪些请求和响应了,但是因为我们用的是https,所有的数据都被加密了,wireshark就不知道该怎么去重组了。有两个办法可以在wireshark中解密 HTTPS 流量:第一如果你拥有 HTTPS 网站的加密私钥,可以用加密私钥来解密这个网站的加密流量;2)某些浏览器支持将 TLS 会话中使用的对称密钥保存在外部文件中,可供 Wireshark 解密使用。但是HTTP/2为了前向安全性,不允许使用RAS秘钥交换,所有我们无法使用第一个方法来解密HTTP/2流量。介绍第二种方法:当系统环境变量中存在SSLKEYFILELOG时,Chrome和firefox会将对称秘钥保存在该环境变量指向的文件中,然后把这个文件导入wireshark,就可以解密HTTP/2流量了,具体做法如下:新建ssl.log文件添加系统环境变量SSLKEYFILELOG,指向第一步创建的文件在wireshark中打开 preferences->Protocols,找到SSL,将配置面板的 「(Pre)-Master-Secret log filename」选中第一步创建的文件这时用Chrome或Firefox访问任何一个https页面,ssl.log中应该就有写入的秘钥数据了。解密完成后,我们就可以看到HTTP/2的包了下图是在demo的主页面抓取的包,可以清楚地看到有哪些HTTP/2请求。HTTP/2协议中的流和可以在一个TCP连接中交错传输,只需建立一个TCP连接就可以完成和服务器的所有通信,我们来看下在demo中的HTTP/2是不是这样的:wireshark下方还有一个面板,里面有当前包的具体信息,如大小、源IP、目的IP、端口、数据、协议等,在Transmission Control Protocol下有一个[Stream index],如下图,它是TCP连接的编号,代表当前包是从哪个TCP连接中传输的。观察demo页面请求产生的包,可以发现它们的stream index 都相同,说明这些HTTP/2请求和响应是在一个TCP连接中被传输的,这么多流的确复用了一个TCP连接。除了多路复用外,我们还可以通过抓包来观察HTTP/2的头部压缩。下图是当前路由下的第一个请求,实际被传输的头部数据有253bytes,解压后的头部信息有482bytes。压缩后的大小减少了几乎一半但这只是第一个请求,我们看看后来的请求,如第三个,实际传输的头部大小只有30bytes,而解压后的大小有441byte,压缩后的体积仅为原来的1/14!如今web应用单是一个页面就动辄几百的请求数,HPACK能节约的流量可想而知。结语在文章开篇,我们列举了HTTP1.x时代的困境,引入并简要说明了HTTP/2的起源;然后对比着HTTP1.x,介绍了HTTP/2的诸多优秀特性,来说明为什么选择HTTP/2;在文章的最后一部分,介绍了如何一步一步搭建一个HTTP/2实例,并抓包观察,验证了HTTP/2的多路复用,头部压缩等特性。最后,您是否也被这些高效特性吸引了呢?动手试试吧参考:HTTP/2 维基百科w3c-preloadHTTP/2 Server Push with Node.js使用 Wireshark 调试 HTTP/2 流量Optimize Your App with HTTP/2 Server Push Using Node and Expressweb性能优化与HTTP/2想要阅读更多技术干货、行业洞察,欢迎关注网易云信博客。了解网易云信。,来自网易核心架构的通信与视频云服务。网易云信(NeteaseYunXin)是集网易18年IM以及音视频技术打造的PaaS服务产品,来自网易核心技术架构的通信与视频云服务,稳定易用且功能全面,致力于提供全球领先的技术能力和场景化解决方案。开发者通过集成客户端SDK和云端OPEN API,即可快速实现包含IM、音视频通话、直播、点播、互动白板、短信等功能。 ...

January 21, 2019 · 2 min · jiezi

HTTP2 基础知识点总结

最近读完《HTTP2 基础教程》,相当于是对 HTTP2 有了个大致的了解,本文已加入我的博客,感谢大家支持!HTTP 发展史互联网刚开始的时候,人们浏览网页只是为了阅读文字,随着时代的不断发展,人们对于网站的需求越来越高,一个网站不仅仅要展示文字,还要展示图片,视频,3D 特效等。1、Web 页面引用的内容每年都在增长,图片,JS,CSS 越来越大,也越来越多。2、Web 所依赖的资源也变得越来越复杂。3、大多数 Web 页面会关联数十个域名的资源,每一个资源都需要经历一次 DNS,TCP,TLS 等。HTTP1.1 阶段人们对网站的需求越来越多,但是 HTTP 协议却发展很慢,HTTP1.1 已经存在 20 年了,却还是当前社会中使用最广泛的协议。以下几个缺点也越来越被大家所关注:1、队头阻塞,在请求响应中如果出现任何问题,剩下的工作都会被阻塞在那次请求应答之后。2、低效的 TCP 利用拥塞窗口:在接收方确认数据包之前,发送方可以发出的 TCP 包的数量。(例如:如果拥塞窗口为 1,那么发送方发出 1 个数据包之后,只有接收方确认了那个包,才能发送下一个)。TCP 中有个概念叫慢启动,目的是为了让新连接搞清楚当前的网络状况,避免给已经拥堵的网络继续添乱。(它允许客户端在收到每个确认回复后额外发送 1 个未确认包,这意味着新连接在收到 1 个确认回复后,可以发送 2 个数据包,在收到 2 个确认回复后,可以发 4 个,以此类推,直到达到上限值)3、臃肿的消息首部虽然 HTTP1 提供了压缩请求内容的机制,但是消息首部却无法压缩,如果算上 cookie,可能每次都会多发送几千个字节。4、受限制的优先级设置HTTP1 没有优先级一说,要么发起请求,要么不发起。Web 性能调优为了解决 HTTP1 的各种问题,大家也总结一下优化方案,我们来依次介绍一下吧。网站运行流程(简化版)输入 URL 并敲回车。根据域名解析 IP 地址。建立 TCP 连接管道。如果是 HTTPS,进行 TLS 握手。服务器端收到请求。输出主体 HTML 。客户端根据 HTML 内的其他资源进行请求。如果想在 HTTP 协议层面做优化,可以考虑下几点:DNS 查询时间TCP 三次握手时间TLS 安全协议时间(秘钥协商,对称加密,非对称加密)DNS 查询优化限制不同域名数量使用 dns-prefetch,在解析主体 HTML 的同时,就会解析制定域名。<link rel=“dns-prefetch” href="//www.xxx.com" />找一家好的外部供应商优化 TCP 连接使用 preconnect 指令,让连接在使用之前就已经建立好。<link rel=“preconnect” href="//www.xxx.com" />尽早终止响应(借助 CDN,让传输更近)。避免重定向利用 CDN 云端重定向。统一域名使用 Web 服务器上的 rewrite 规则,避免重定向。缓存策略我们常常使用缓存来避免不必要的请求,但要使用缓存必须遵守着两个概念:多用户之间可共享,能够接受一定程度的旧数据。纯静态不变的内容,可以永久缓存。CSS/JS 等个性化资源,缓存时间是会话(交付)平均时间的两倍。强缓存使用 Expires 首部,将资源失效的日期告诉客户端,在失效如期之前,客户端都会直接使用缓存中的资源而不会发起请求。使用 Cache-Control 首部,进行更多的定制化缓存:max-age 表示资源会缓存的具体时间no-cache 不使用本地缓存。需要使用缓存协商。no-store 直接禁止游览器缓存数据,每次用户请求该资源,都会向服务器发送一个请求,每次都会下载完整的资源。public 可以被所有的用户缓存,包括终端用户和 CDN 等中间代理服务器。private 只能被终端用户的浏览器缓存,不允许 CDN 等中继缓存服务器对其缓存。协商缓存在请求中包含 HTTP 首部 Last Modified,仅当最新内容在首部中制定的日志之后被更新过,服务器才返回完整内容,否则返回 304.在请求实体中包含实体校验码 ETag,它唯一标识所请求的资源,服务器会比较当前 ETag 与请求首部中收到 ETag,如果一致,就返回 304.压缩和代码简化删除 html 文档中的注释,换行,空格等,减少资源大小。避免阻塞 CSS/JS由于 JS 在处理时,会阻止其他任何资源的下载和渲染,可能会带来不必要的延迟。CSS 影响页面可视化效果,建议先请求。JS 则需要正确用好 defer, async。图片优化一张图片中往往并不是只有图片内容,往往还包含有一些图片元信息:地理位置信息,时间戳,尺寸,像素等。而这些信息对于页面并没有声明用,还会增加图片的大小,建议删除掉图片元信息,只保留真正有用的部分。另外,页面中的图片,尽量不要使用 CSS 去拉伸或者缩放,需要多大的图片,就返回多大的图,避免资源浪费。回归正题 HTTP2 核心概念上面介绍了 HTTP 的历史以及现阶段使用最广泛的 HTTP1 ,接下来我们来介绍一下 HTTP2 ,看一看 HTTP2 是怎么解决 HTTP1 所遗留的问题?首先,要搭建 HTTP2 服务,必须配套使用 HTTPS 安全策略,才能得到浏览器的支持。HTTP2 其实并没有要求必须使用 HTTPS,只是人们正好认识到 HTTPS 的重要性,而 HTTP2 正好出来,就不谋而合的结合在一起了。HTTP2 分层分帧层,即 HTTP2 多路复用能力的核心部分。数据或 HTTP 层,其中包含传统上被认为是 HTTP 及其相关数据的部分。分帧会来来如下好处:1、二进制协议:HTTP2 的分帧层是基于帧的二进制协议,方便机器解析。2、首部压缩:仅仅使用二进制协议还不够,HTTP2 的首部还会被深度压缩。3、多路复用:当你在使用 HTTP2 传输链接的时候,不必等待上一个请求结束后才进行下一个请求,请求和响应可以交织在一起。帧HTTP2 是基于帧的协议,为了将重要的信息都封装起来,让协议的解析方可以轻松的阅读、解析并还原信息。相比之下,HTTP1 并不是基于帧的,而是以文本来分隔,服务器只能根据文本换行符来拆分请求数据。使用 HTTP1 可能会产生以下问题:换行符对于每个平台可能有兼容性问题,例如有些平台采用<crlf>,有些平台采用<lf>一次只能处理一个请求或响应,完成之前不能停止解析。无法预判解析需要多少内存,如果一行的信息量太大,超出了内存,会返回 400 错误帧有着严格的结构格式,有了帧,处理协议的程序就能预先知道会受到什么,从而采取对应的解析方法。可以把帧理解为一个对象:var frame = { length:‘帧负载的长度’, Type:‘类型’, Flags:‘帧的标识’, R:‘保留位’, Stream Identifer :‘每个流唯一ID’, Frame Payload:‘真实的帧内容’}这样以来,实现和维护都会简单很多,不用等到一个请求完成以后才进行下一次请求,请求和响应可以交错甚至可多路复用。流流是 HTTP2 链接上独立的,双向的帧序列交换。可以将流看作在连接上的一些列帧,他们构成了单独的 HTTP 请求和响应。1、消息:泛指 HTTP 中一个请求或一个响应。2、流量控制:当一段接收并消费被发送的数据时,它将发出一个 WINDOW_UPDATE 帧,用来表示其更新后的处理字节的能力。确保一个流不会影响到其他的流。3、优先级首先请求网页上最重要的元素,以最优的顺序获取资源,由此来优化页面性能。通过 HEADERS 帧可以指明某些对象和其他对象的依赖关系。通过 PRIORITY 帧,可以告诉服务器如何确定具有共同依赖关系的对象的优先级。服务器推送提升单个资源性能的最佳方式,就是在它被用到之前就放到了浏览器的缓存里,服务器端可以主动将资源发给客户端,这可能是因为它知道客户端不久后将会用到该资源。如果服务器决定推送一个对象,会通过 PUSH_PROMISE 帧去传递将会被推送的资源。首部压缩之前说到,HTTP1 中没有对首部进行压缩,这会在每次请求中发送大量的冗余首部,而 HTTP2 则解决了这个问题。HTTP2 没有使用 gzip 压缩,而是使用 HPACK,因为 GZIP 压缩有泄漏加密信息的风险,简称 CRIME。CRIME 原理:攻击者在请求中添加数据,观察压缩加密后的数据量,如果变小了,就证明注入的数据和请求中的其他内容有重复,进而搞清楚所有的加密数据内容。HTTP2 的 HPACK 原理:当客户端请求时,会根据发送的首部信息,建立一张表:索引首部名称值62header1foo63header2bar64header3baz服务器端再接收到数据的时候也会创建一张与之对应的表。客户端在发送下一个请求的时候,如果首部是相同的,就不用发送 Header 头,只用发送索引号就行了,服务器端会索引去还原对应的首部信息。并非一定能从 HTTP2 中获益之前说了这么多 HTTP2 的好处,你是否已经觉得升级 HTTP2 已经迫不及待了呢?但是 HTTP2 页不是完美的,这一小节我们来介绍一下 HTTP2 的一些坑。1、关于丢包。之前说到,HTTP2 采用多路复用,可以让多个请求在同一个 TCP 连接中进行传输,但是由于 HTTP2 是单链接架构,如果唯一的连接发生了丢包,所有的工作都会受到影响,这其实是 HTTP2 中比较大的坑。HTTP1 在请求时建立了多个连接(和浏览器相关,一般为 6 个),相对于 TCP 的初始拥塞窗口更大。当有一个连接发生丢包时,不会影响到其他请求。对比之下: HTTP2 比 HTTP1 更容易丢包。2、关于服务器端推送。之前说到,服务器端推送可以主动给客户端推送资源,用来减少客户端发起请求数量。可是这也可能带来一个问题:如果推送的资源在客户端已经缓存过,那就是多此一举了,所以在做服务器端渲染时,一定要和客户端的缓存策略结合起来。HTTP2 反模式HTTP2 部署之后,之前针对于 HTTP1 的优化方案有可能就不适用了,我们来看一下都有哪些。域名拆分在 HTTP1 中,会将一些静态资源存在多个 cdn 服务器(因为浏览器对同一个域名下的资源请求并发数有限制,一般是 6 个)。但是使用 HTTP2 之后就不必要了,因为 HTTP2 采用多路复用,再多的资源也都能够进行并发请求。资源内联在 HTTP1 中,我们可会对一些小图片直接打包成 base64 格式,用来减少请求。但内联无法利用缓存优势,应具体情况具体分析。资源合并(雪碧图等)由于多路复用的原因,多个小图片可以并行请求,雪碧图也不是很有必要了。禁用带 cookie 的域名在 HTTP1 中,由于无法压缩首部,会启动一个无 cookie 的服务器专门用来存放某些静态资源,用来减少不必要的 cookie 传输。在 HTTP2 中,首部信息会被 HPACK 算法优化,大大减少了首部字节,而且不用心增一个无 cookie 的服务器,所以建议取消掉禁用 cookie 域名的方式。资源预取高数浏览器,之哟啊有可能就继续下载可缓存资源,并把这些资源缓存起来,这个还是有必要。<link rel=“prefetch” href=“xxx.css”>展望未来1、HTTP 是使用 TCP 作为传输层的协议,由于 TCP 是可靠的,拥塞控制的协议,在进行一次连接时,会发生三次握手,断开连接时会有 4 次挥手,所以有人提出可以用 UDP 这种简单,快速的协议去代替。Google 开发的 QUIC 是极快的 UDP 网络连接,提供了 HTTP2 等效的,多路复用,流量控制,TSL 等效的安全机制,以及 TCP 等效的连接语义、可靠性、拥塞机制。2、TLS(传输层安全协议,用来实现 HTTPS)也在不断地进行改进。在 TLS1.3 版本中 新连接只需要一次往返(目前最少是三次),如果是恢复连接,则不需要往返延时。 ...

December 17, 2018 · 2 min · jiezi

详解http-2头部压缩算法

准备工作使用wireshark抓取http2.0请求 点击查看正式内容问题背景HTTP1.x的header中的字段很多时候都是重复的,例如method:get、status:200等等,随着网页增长到需要数十到数百个请求,这些请求中的冗余标头字段不必要地消耗带宽,从而显著增加了延迟,因此,Hpack技术应时而生。Hpack思想简介首先介绍下压缩的概念(比较简单,熟悉的可以跳过):通信的双方各拥有一本字典,记录着某些字符对应的文本内容,例如x代表危险,y代表撤退,z代表进攻等;消息发送方根据字典生成消息文本比如’x,y’接收方接收到消息后,根据字典还原内容:“危险,撤退”这个例子已经简单介绍了压缩的好处:可以在传输的过程,简化消息内容,从而降低消息的大小官方文档里的对Hpack的主要思想说明:将header里的字段列表视为可包括重复对的name-value键值对的有序集合,分别使用8位字节表示name和value当字段被编码/解码时,对应的字典会不断扩充在编码形式中,header字段可以直接表示,也可以使用header field tables 中对应的引用。因此,可以使用引用和文字值的混合来header字段列表。文字值要么直接编码,要么使用静态huffman代码编码器负责决定在标题字段表中插入哪些标题字段作为新条目。解码器执行对编码器规定的报头字段表的修改,重建处理中的报头字段列表以上摘自RFC 7541协议使用翻译工具直接翻译-_-,所以看起来有点艰涩,没关系,先往下看引子-压缩效果比对由于理论内容比较枯燥,所以先来几张图看一下效果,这里使用wireshark来抓取对同一个页面的两次请求,查看对比。初次请求头部长度412,解压完 690 压缩完大概是原来的60%二次请求头部长度172,解压完 690 压缩完大概是原来的25%简单分析以cookie这个字段为例,在上述两次请求中:第一次请求时cookie所占的字符长度为36:第二次请求时cookie所占的字符长度为1:所以通过简单观察,我们可以简单得出以下结论:头部压缩可以减小请求的头部大小(显而易见)二次压缩的压缩率会更高,(后面会解释为什么)过程简述简单描述一下Hpack算法的过程:消息发送端和消息接受端共同维护一份静态表和一份动态表(这两个合起来充当字典的角色),每次请求时,发送方根据字典的内容以及一些特定指定,编码压缩消息头部,接收方根据字典进行解码,并且根据指令来判断是否需要更新动态表技术细节首先介绍一下前面在说明“压缩过程”时,提到的字典。在Hpack中,一共使用2个表来充当字典的角色:静态表和动态表。静态表静态表很简单,只包含已知的header字段。点此查看完整的静态表,分为两种:name和value都可以完全确定,比如:metho: GET、:status: 200只能够确定name:比如:authority、cookie第一种情况很好理解,已知键值对直接使用一个字符表示;第二种情况稍微说明下:首先将name部分先用一个字符(比如cookie)来表示,同时,根据情况判断是否告知服务端,将 cookie: xxxxxxx 添加到动态表中(我们这里默认假定是从客户端向服务端发送消息)动态表动态表最初是一个空表,当每次解压头部的时候,有可能会添加条目(比如前面提到的cookie,当解压过一次cookie时,cookie: xxxxxxx就有可能被添加到动态表了,至于是否添加要根据后面提到的指令判断)动态表允许包含重复的条目,也就是可能出现完全相同的键值对为了限制解码器的需求,动态表大小有严格限制的索引地址空间静态表和动态表一起组成一个索引地址空间。设静态表长度为s,动态表长度为k,那么最终的索引空间如下:<———- Index Address Space ———-><– Static Table –> <– Dynamic Table –>+—+———–+—+ +—+———–+—+| 1 | … | s | |s+1| … |s+k|+—+———–+—+ +—+———–+—+ ^ | | V Insertion Point Dropping Point其中:索引1-s是静态表,s-k是动态表,新的条目从在动态表的开头插入,从动态表末尾移除有了这个索引空间以后,header的字段一共有以下几种表示方法:直接用索引值来表示(比如2表示method:get)字段的name使用索引值表示,字段的value直接使用原有字面的值的八位字节序列或者使用静态哈夫曼编码表示字段表示法header字段的表示法一共分2种,下面逐一说明。数字表示法数字主要用来表示上文中索引空间的索引值,具体的规则如下:先用限定位数的前缀表示,如果范围足够那就直接表示(限定位数是指下图中的扣除xxx剩余的长度,xxx的具体含义见下一节-动态表更新指令以及表示)如果范围不够大,那么接下来每次增加8个字节来表示8个字节的最高位都作为标志位,表示是否要继续向下延续(解码的时候要用到)接下来看官方的一些例子帮助理解:1. 用5位前缀表示10首先这里限制位数为5,由于10小于2^5-1,可以直接表示为01010,结果为:0 1 2 3 4 5 6 7+—+—+—+—+—+—+—+—+| X | X | X | 0 | 1 | 0 | 1 | 0 | 10 stored on 5 bits+—+—+—+—+—+—+—+—2. 用5位前缀表示13371337>2^5-1,那么前面5位只能表示到31,剩余1337-31 = 1306接下来:1306>2^7 =128(八位字节第一位是标志位,所以表示范围只有2^7-1)I % 128 == 26,26用7位2进制表示是0011010,由于I >128 还需要继续延续,所以标志位取1,得到第二行应该是10011010I / 128 = 10,10用7位2进制表示是0001010,标志位取0即可所以最终结果如下: 0 1 2 3 4 5 6 7+—+—+—+—+—+—+—+—+| X | X | X | 1 | 1 | 1 | 1 | 1 | Prefix = 31, I = 1306| 1 | 0 | 0 | 1 | 1 | 0 | 1 | 0 | 1306>=128, encode(154), I=1306/128| 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 10<128, encode(10), done+—+—+—+—+—+—+—+—+3. 直接从边界开始表示42直接从边界开始,也就是使用8位前缀,42小于2^8-1=255 所以直接表示: 0 1 2 3 4 5 6 7+—+—+—+—+—+—+—+—+| 0 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 42 stored on 8 bits+—+—+—+—+—+—+—+—+字符串表示法header的字段可以用字符串文本来表示,具体的规则如下: 0 1 2 3 4 5 6 7+—+—+—+—+—+—+—+—+| H | String Length (7+) |+—+—————————+| String Data (Length octets) |+——————————-+H是一个标志位,表示该字符串的八位字节是否被哈夫曼编码过String Length:,表示用于编码的字节位数,具体的规则就是刚刚提到的7位前缀表示法String Data:字符串编码过的数据,如果h为0,则编码数据是字符串文字的原始八位字节;如果H是“1”,则编码数据是字符串文字的huffman编码。huffman编码参见动态表更新指令以及表示情况1:整个键值对都在现有的索引空间中这种情况下,第一个字节固定为1,然后用7位前缀法表示索引的值 0 1 2 3 4 5 6 7+—+—+—+—+—+—+—+—+| 1 | Index (7+) |+—+—————————+Figure 5: Indexed Header FieldAn indexed header field starts with the ‘1例如10000010,表示索引值为2,查找静态表可知,对应的header字段是method:GET注意我们前面说索引空间的时候提到,索引空间地址是从1开始的,0的话会被视为错误,也就是10000000解码时会出错。情况2:name在索引空间,但是value不在,且需要更新动态表这种情况下,前两位固定为01,后面6位表示索引值,取到对应的name,例如01010000对应32,查静态表可知name是cookie,接下来使用字符串表示法表示对应的value字段,在解码之后,这个字段就被加到动态表中,下次编码的时候会直接使用情况1,(这里也就说明了为什么后续请求压缩程度更大,因为动态表在不断扩充,扩充的界限请看官方文档这里暂时不说明) 0 1 2 3 4 5 6 7+—+—+—+—+—+—+—+—+| 0 | 1 | Index (6+) |+—+—+———————–+| H | Value Length (7+) |+—+—————————+| Value String (Length octets) |+——————————-+情况3:name和value都不在索引空间,且需要更新动态表这种情况和上面的很相似,只要补上name部分的字符串表示,并且把index值设置为0即可。 0 1 2 3 4 5 6 7+—+—+—+—+—+—+—+—+| 0 | 1 | 0 |+—+—+———————–+| H | Name Length (7+) |+—+—————————+| Name String (Length octets) |+—+—————————+| H | Value Length (7+) |+—+—————————+| Value String (Length octets) |+——————————-+观察情况2和情况3可知,如果需要更新动态表,前两位标志位都是01情况4:name在索引空间,但是value不在,且不需要更新动态表这种情况,前四位固定为0000,其他和情况2一致,0 1 2 3 4 5 6 7+—+—+—+—+—+—+—+—+| 0 | 0 | 0 | 0 | Index (4+) |+—+—+———————–+| H | Value Length (7+) |+—+—————————+| Value String (Length octets) |+——————————-+情况5 name和value都不在索引空间,且不需要更新动态表同理,前四位固定为0000,其他和情况3一致, 0 1 2 3 4 5 6 7+—+—+—+—+—+—+—+—+| 0 | 0 | 0 | 0 | 0 |+—+—+———————–+| H | Name Length (7+) |+—+—————————+| Name String (Length octets) |+—+—————————+| H | Value Length (7+) |+—+—————————+| Value String (Length octets) |+——————————-+观察情况2和情况3可知,如果需要更新动态表,前两位标志位都是0000情况6 name在索引空间,但是value不在,且绝对不允许更新动态表这种情况下和情况4基本一致,只是前四位固定为0001,区别在于:不需要更新表示,本次的发送过程不更新该字段到动态表;如果有多次转发,那么并不对转发做要求绝对不允许更新表示,如果这个请求被多次转发才到目标,那么转发的所有中间对于该字段也必须采用相同的处理方案0 1 2 3 4 5 6 7+—+—+—+—+—+—+—+—+| 0 | 0 | 0 | 0 | Index (4+) |+—+—+———————–+| H | Value Length (7+) |+—+—————————+| Value String (Length octets) |+——————————-+情况7 name和value都不在索引空间,且绝对不允许更新动态 0 1 2 3 4 5 6 7+—+—+—+—+—+—+—+—+| 0 | 0 | 0 | 1 | 0 |+—+—+———————–+| H | Name Length (7+) |+—+—————————+| Name String (Length octets) |+—+—————————+| H | Value Length (7+) |+—+—————————+| Value String (Length octets) |+——————————-+和上面一种情况同理,就略过了。实例分析主要内容已经都说完了,接下来抓一些请求来看看具体的内容,比如直接抓取segment下的请求。在这里method字段,就是前面提到的情况1–直接在静态表查询就可以得到整个键值对,对应的索引值为2。 查看底下的编码10000010,第一位1表示整个键值对在索引空间存在,后面的0000010=2表示索引地址。接下来看authority字段,我们抓第一次和第二次请求的进行对比:第一次请求可以看出,这个字段符合前面提到的情况2:第一次编码是01000001,表示name直接使用索引,索引值为1,且value不在索引空间中,后面的部分表示具体的value值第二次请求第二次请求,发现已经是直接使用索引空间的值(因为前一次请求已经要求更新到动态表),所以本次只要一个字符长度直接表示这个字段110001101,第一个1表示情况1,后面1001101=64+8+4+1 =77 也就是此时对应的索引值小结我们前面提到动态表会随请求增加不断更新,但是动态表其实是有大小限制的,因此动态表在增加条目时也可能会删除条目,具体的更新规则等限于篇幅(没错,不是因为懒)不在本文更新。还有就是相关的huffman编码等也不在此说明,本文主要还是针对Hpack算法的过程和编码规则做一些说明。主要参照RFC 7541协议惯例:如果内容有错误的地方欢迎指出(觉得看着不理解不舒服想吐槽也完全没问题);如果对你有帮助,欢迎点赞和收藏,转载请征得同意后著明出处,如果有问题也欢迎私信交流,主页有邮箱地址 ...

November 14, 2018 · 3 min · jiezi