揭秘HTTP/3优先级

编者按 / 相关于HTTP2,HTTP/3的优先级更加简略,浏览器厂商更或许完结一致的优先级策略。本文来自老朋友Robin Marx,已获授权转载,感谢刘连响对本文的技能审校。

翻译/ 核子可乐

技能审校 / 刘连响

原文链接/calendar.perfplanet.com/2022/http-3…

假如咱们做过Web功用调优,或许会听说过HTTP资源优先级。这波趋势从去年开端风头愈劲,由于Chromium经过新的fetchpriority特点引进了所谓“优先级提示”,答应开发者进行优先级调整。别的,HTTP/2和HTTP/3之间的优先级体系也发生了变化。

但是,这个优先级终究是什么?它是怎样起效的?为什么有必要加以操控?并且最要害的,是不是一切浏览器都对资源的重要性拥有共识(当然不是)?顺着这个思路,咱们就一同说道说道:

  • 优先级终究是什么?

  • 资源加载推迟

  • 优先级信号

  • 浏览器差异

  • 原始协议细节

  • 主要定论(或许是您最感兴趣的部分)

  • 服务器差异

  • 总结

留意:HTTP优先级是个十分广泛的论题,乃至专门写篇博士论文都没问题。所以在本文中,我故意疏忽掉了许多细微差别,只专心评论几个要害点。

01 优先级终究是什么?

HTTP资源优先级,归于主要面向HTTP/2(H2)和HTTP/3(H3)的概念。在HTTP/1.1(H1)中,浏览器往往会翻开多个TCP衔接(每个域最多6个),且每个衔接每次仅加载1个资源/文件。这儿的优先级隐式存在,代表在可用衔接上首要恳求资源。

但在H2和H3这边,咱们目标是仅运用单一TCP/QUIC衔接来进步功率。但假如单一衔接也只能像H1那样每次只要一个资源处于“活动”,那必定不利于功用体现。所以H2和H3能够一起发送多个恳求。但请千万留意,这并不是说这多个恳求就一定能一起得到充分呼应。由于在恣意给定时间,衔接所能发送的数据量都受到拥塞和流量操控等要素的约束。

揭秘HTTP/3优先级

图一:典型的拥塞操控算法对慢启动管理得很严,对后续推迟增长则体现得较为宽容。

特别是在衔接启动时,咱们只能在每次网络往复中发送有限数量的数据,由于服务器需求等待浏览器确认其已成功接纳到每波突发数据。也便是说,服务器需求挑选究竟先呼应多个恳求中的哪一个。

举例来说,浏览器现已加载了.html页面,并在一个H2/H3衔接上一起恳求了3种资源:一个被延期的JS文件(100 KB)和两个.jpg图画(别离为300和400 KB)。假定服务器在此次往复中只能发送50 KB数据(约35个数据包,详细数字取决于各种要素),那现在就得决议怎样填充这35个数据包了。所以,究竟该首要发送什么?

咱们能够说JS总是最重要的(高优先级),哪怕存在延期也是。或许,咱们也能够说其间一张图画或许是Largest Contentful Paint(审校者注:LCP,即最大内容制作,用于监控网页可视区内“制作面积”最大的元素开端呈现在屏幕上的时间点。)的候选对象,所以应该优先发送其间一张(但究竟是哪张?)。咱们乃至能够说jpg图画能够逐步闪现,所以别离给每张图画25 KB的配额(这便是所谓资源数据的「交织」或多路复用)。但假如延期的JS便是实践加载LCP图画的主体……那是不是也得给它分配点带宽?

揭秘HTTP/3优先级

图二:能够用多种办法为三种资源分配带宽。

很明显,这儿服务器做出的挑选会对各项Webperf目标发生重大影响,究竟某些资源数据将始终被置于“更高优先级”的“重要”数据之后。假如先发送JS,那么图画会推迟1次或经历多次网络往复,反之亦然。这一点对(烘托堵塞)JS和CCS的影响尤其明显,由于其需求完好下载后方可应用/履行。

揭秘HTTP/3优先级

图三:假如资源以交织/多路复用办法加载(上方),则会拖慢其彻底加载的速度。

留意:这便是我反对人们总说什么H2和H3答应并行发送多个资源的理由,由于这底子就不叫真实的并行!H1反而比H2/H3并行得多,由于前者有6个独立衔接。充其量,H2/3数据只能算在线路上交织或多路复用(例如将配额别离给予两张图画),但常规呼应仍是按次序发送(先是完好加载第一张图画,之后是第二张)。所以作为一个“学究”,我仍是更倾向于并发资源(或许叫并行恳求和多路呼应)。

这儿的问题在于,服务器并没有满足的信息来做出最佳挑选。现实上,它乃至不知道JS文件在HTML被符号为延期(defer),由于浏览器的HTTP恳求中并不包括这段上下文(并且服务器往往不会亲身解析HTML来发现这些修饰符)。相同的,服务器也不知道图画是否当即可见(例如,在viewport中)或许没有可见(用户需求向下滚动才能看到轮播中的第二张图)。至于新鲜上架的fetchpriority特点,服务器更是闻所未闻。

所以拥有满足上下文来决议该先加载哪种资源的,其实是浏览器。它需求一种办法将首选项传递给服务器,而这便是HTTP优先级机制的由来:一种浏览器向服务器发送资源恳求详细次序的标准化办法。

资源加载推迟

这儿要提醒咱们,优先级并不是影响实践资源交给次序的唯一要素。究竟优先级决议的仅仅是怎么处理一起处于活动状况的多个恳求。有些朋友或许以为,关于HTTP/2和HTTP/3,浏览器能够在HTML中发现资源后当即提出恳求,再单靠优先级排序来取得正确的呼应。不好意思,做不到的。

实践上,一切浏览器都或多或少具备一些(高级或根底)逻辑,用于主动推迟某些恳求,即便在发现资源之后也是如此。举个简略的比方,预取资源通常会在中的元素中指示,但仅在当前页面加载完结时由浏览器恳求。

另一个比方便是Chromium的“紧凑办法”,它会主动推迟掉不太重要的资源(例如HTML中的图画、CSS和JS),直到(大部分)更重要的资源加载完结。再有,一起激活的预加载资源也有约束。

咱们能够经过以下瀑布图看到,部分资源即便被发现得更早,也只会在一段时间后才被恳求。

揭秘HTTP/3优先级

图四:在Safari中,这些资源在被发现后也不会被一起恳求。

在实践傍边,浏览器一起发送哪些恳求、各恳求之间的优先级联系间存在着适当杂乱的相互效果。受篇幅所限,本文就不过多评论了。当然,我接下来要介绍的测验办法的确能够完结这种影响分析,这也是我个人的下一步研究方向。

02优先级信号

检查浏览器开发工具中“Network”选项卡下的优先级列(参见上图四),就会看到由高到低(或许类似排序)的文本数值。您或许觉得这便是发送至服务器的内容,但很惋惜,状况并非如此。

特别是在HTTP/2中,有一套更高级的体系在起效果,便是所谓“优先级树”。在这儿,资源以树状数据结构摆放。资源在树中的方位(父项和兄弟项别离是什么)与所相关的“权重”会影响其何时取得带宽、取得多少带宽。在恳求资源时,浏览器会运用特殊的附加HTTP/2音讯(PRIORITY帧)向服务器表达该资源在树中的方位。

揭秘HTTP/3优先级

图五:Firefox运用杂乱的HTTP/2优先级树。

这套体系如此灵敏和强大,但现实也证明其十分杂乱,乃至是过度杂乱。以至于即便在今日,不少HTTP/2在详细完结上都有严重的过错,另一些仓库则底子无法完结(直接疏忽浏览器的信号)。不同浏览器运用该体系的办法也存在巨大差异。

因而在HTTP/3傍边,咱们才决议树立一套更简略的体系,这最终成了RFC 9218:HTTP的可扩展优先级方案。这种办法没有完好的树状结构,只要8个数字的优先级(即「紧迫度」,取值在0到7之间)外加一个“增量”布尔符号,用于指示资源能否与其他资源交织(渐进式jpg:能够交织;烘托堵塞JS:最好不要交织)。默许状况下,资源的紧迫度为3且非增量。

揭秘HTTP/3优先级

图六:新体系运用两个参数——紧迫度与增量

其间的概念十分简略:服务器应首要发送具有最高非空优先级组内的一切资源(u0应在u1前处理等),之后再继续下一个组。因而只要还有一种u=0的资源处于活动状况(并且咱们有数据要发送给它),就不应为其他紧迫组发送数据。在同组之内,资源则按恳求次序发送(由先到后),因而中靠前的JS会在中靠后的JS前交给。

留意:假如一个紧迫组内只要增量或非增量资源,那么状况当然很简略(审校者注:“incremental”没有十分恰当的中文说法,暂且译成“增量”。)。而一旦增量与非增量资源混合起来(比方前文示例,一个非增量JS和两张增量图画均在u=3组内),处理难度就上来了。咱们要不要先把完好的JS发送完(由于它是非增量的)?仍是说,咱们该为图画供给部分先期带宽(由于它们的优先级相同,并且能够增量发送)?很惋惜,RFC自身并没有给出详细指导,由于每种选项都有利害。

新体系在发送紧迫和增量信号的办法上也更简略:这儿运用的并非特殊的HTTP/3音讯,而是名为priority的新文本HTTP标头。这种整体更简略的办法降低了完结和调试难度,并且有望带来比H2体系更好的支撑并削减bug(剧透一下,其实也还做不到)。

揭秘HTTP/3优先级

图七:新体系运用新的“Priority”HTTP标头。

但有时候好主意就只是个主意。实践HTTP标头只能用于表达资源的初始优先级,一旦稍后需求更新优先级(比方推迟加载的图画最初取得低优先级,但在滚动至视图内时需求切换至高优先级),那单靠HTTP标头就完结不了了。为此,咱们仍是需求特殊的H3(和H2!)二进制音讯:PRIORITY_UPDATE帧。这些细节对大多数人来说并不重要,但我仍是想做做着重,究竟HTTP标头有Firefox和Safari在用,但Chromium却底子没用。出于“种种原因”,谷歌只运用PRIORITY_UPDATE框帧来表明初始优先级(会当即覆盖掉默许优先级)。

需求留意的是,这种新方案也并非HTTP/3所独有。其目标便是随时间推移,将功用移植到现有H2完结傍边(但据我所知,现在还没有H2仓库实践选用)。别的,它之所以被称为“可扩展”的优先级体系,是期望能在未来引进“紧迫度”和“增量”以外的更多其他参数。

03浏览器差异

正如前文说到,不同浏览器运用杂乱HTTP/2体系的详细办法也有很大差异。各种干流浏览器引擎(Chromium、Firefox、Safari等)会生成天壤之别的优先级树和信号。

但在接下来的部分,我将只重视HTTP/3上的新体系,究竟一切三种干流浏览器都能支撑。我想搞清它们在新体系的完结办法上是否还有差异。但经过检索,我发现只要Chrome发布了关于详细办法和逻辑的敞开文档,而Safari和Firefox那儿压根没有任何研究资料。所以,我只好亲身动手了!

开端之后,我立刻遇到了两个问题:

  1. 新信号的查询十分困难,由于现在还没有相应的支撑工具。这些信号不会以原始办法呈现在浏览器的开发工具中,也不会呈现在WebPageTest内。所以我决议修改aioquic HTTP/3服务器,为优先级信号添加一些额外的日志记载。

  2. 我四处检查,但没有哪个测验页面能包括一切或许影响优先级的悉数资源加载项(异步/延期、懒加载/急加载、fetchpriority、预加载/预取等)。所以我创建了自己的测验页面,其间包括多达36种不同的状况。

之后,我在自定义HTTP/3服务器上托管了自定义测验页面,并别离用三款浏览器进行加载。我保存了来自浏览器的HAR(审校者注:HAR即HTTP Archive format, 一种HTTP恳求存档格局。)文件和来自服务器的日志,想搞清楚浏览器经过网络究竟发送了什么内容。感兴趣的朋友能够在GitHub上(github.com/http3-prior…

原始协议细节

下面,我会从原始底层成果开端评论。假如咱们不关心细节,能够直接跳往下一节阅览宏观层面的解析。

揭秘HTTP/3优先级

图八:部分原始成果。

首要,如前所述,Chromium只运用PRIORITY_UPDATE结构,而未运用HTTP标头。Firefox和Safari则相反,仅运用标头。受测验页面的性质决议(仅包括初始加载),所以我无法查询浏览器是否真的发送了更新。但在原理上,Chromium必定会为图画履行此操作(先将其视为低优先级,之后在图画需求可见时再更新为高优先级)。在这种状况下,Chromium只会发送2个PRIORITY_UPDATE帧,其一用于初始优先级,其二用于后续实践更新。再聊点纯技能细节:初始PRIORITY_UPDATE的发送次序在HTTP标头之前。

第二个重要差异,便是增量参数的运用。Chromium和Firefox会默许将其设置为“封闭”,便是说服务器不会在多个资源间分配带宽;最重要的资源有必要先被彻底加载,之后再转往下一个。Safari则相反:它会为一切资源类型设置增量参数,包括烘托堵塞的JS和CCS资源。但前面也提过,这两种作法其实各有问题。

别的需求着重一点,Chromium的履行逻辑现已有了清晰的调整方案。据我所知,后续更新会为Chromium内的一切内容设置增量参数,除了(高优先级的)JS和CSS文件。这有助于解决一些长期存在的过错,比方大规模HTML下载或许无端导致烘托堵塞CSS发生推迟。开发团队明显意识到Chromium采纳的纯按序加载机制的缺陷。更重要的是,这意味着与HTTP/3相比,Chromium会对HTTP/2运用不同的优先级逻辑(HTTP/2也未运用类增量参数机制)。视详细设置而定,这项调整方案或许会对页面发生很大影响,所以建议咱们尽快切换至HTTP/3。最终需求留意的是,Firefox也有类似的状况,现已在HTTP/2中运用增量信号,但在HTTP/3中却没有。

第三,不同览器间的信号运用办法也有细微差别。例如,Chromium和Firefox不会清晰发送“u=3”的信号,由于这是服务器所遵从的默许值。但Safari却会清晰发送“u=3,i”。此外,浏览器运用的紧迫度值也有不同。在内部,Chromium和Safari运用的是5档优先级(分为:最高、高、中、低、最低),而Firefox似乎只运用4档。Chromium的紧迫度数值为(0,1,2,3,4),Safari分得更开(0,1,3,5,7),Firefox则直接跳过了0(1,2,3,4)。这应该不会影响服务器的资源交给办法(紧迫度值的比较联系更重要,详细运用哪个数字其实无所谓),但风趣的是,即便是这样一套简略体系,市面上也呈现了三种不同的完结办法。

关于以下主要定论,我挑选最高、高、中、低、最低的5档紧迫度区分办法(Firefox只到4,所以不存在「最低」这档)。

主要定论

跟咱们料想的一样,除了协议细节之外,浏览器为不同资源类型和加载办法分配的重要度也有很大差异。下面我交评论几组资源类型(HTML与字体、CSS、JS、图画、提取)以及三款浏览器间的一些主要差异。请留意,我会尽量避免去评判哪种办法更好,究竟这取决于特定页面设置和运用到的功用。期望咱们尽量把我给出的定论,当作在不同浏览器之上展开功用调优的参考定见。

揭秘HTTP/3优先级

走运的是,三款浏览器都以为主HTML文件是最重要的(虽然实践上也未必)。而在字体方面,三者的权重就不同了。Chromium以为字体跟HTML本体一样重要,而Safari和Firefox则将其置于中乃至是低优先级。特别是跟CSS和JS街霸减仓资源的优先级进行比较后,咱们发现Chromium对字体的重视程度的确远超别的两款浏览器。字体预加载也很风趣:在这方面,Chromium实践上是降低了优先级(或许是由于预加载对应的是将要需求、而非当即需求的资源)。Firefox则选用相反的逻辑,为预加载字体分配了更高的优先级。

揭秘HTTP/3优先级

对中CCS进行加载的要害差异在于,Chromium会将其视为最高优先级,等同于HTML文件;而其他两种浏览器则将悉数CSS都视为“高”类别。关于HTML中排序靠后的CSS(在我的测验用例中乃至是垫底方位),Chromium风趣地将其列入“中”类别(这是对的,究竟它并不是真实的「烘托堵塞」)。一切浏览器都精确将带有media=”print”的CSS放置在了最低优先级(除了Firefox,由于它没有「最低」这项)。

揭秘HTTP/3优先级

至于JS,几乎能够说是随处优先。一切浏览器都要求积极处理中的沉浸堵塞JS。而除此之外,关于预加载JS,只要Firefox调低了其优先级(或许是选用了Chromium的字体处理逻辑),并且一切浏览器都正确地明显下调了预取JS的优先级。但异步/延期部分有点古怪,Chromium和Firefox都为其分配了相同的优先级。就个人而言,我觉得延期的定义就该决议它优先级更低。Safari就更古怪的,它反倒把延期的优先级设得比异步更高(我真的彻底理解不了这一点)。别的,Safari给稍后加载的JS分配了与内相同的优先级,Chromium和Firefox则以为前者应该比内的JS优先级低。最终,Safari几乎把一切CSS和JS都划入了“高”优先级序列,意味着不那么重要的文件也或许拖慢要害资源,特别是在Safari对一切恳求都运用“增量”参数的背景下。

揭秘HTTP/3优先级

关于图画,各浏览器的思路比较一致。但其间有个风趣的例外,即Safari调低了懒加载隐藏图画的优先级(我这个测验页面中display: none

内的一般揭秘HTTP/3优先级),但Firefox和Chromium则以为其优先级应该与可见图画相同。别的还有两个清晰的比方,能够阐明单纯着重优先级还不够:预加载和懒加载都底子不会影响到图画优先级!这些功用只在实践恳求资源时才会起效(详见前文),所以咱们才需求新的FetchPriority特点,这实践上对优先级机制起到了增强效果(Chromium现已迈出了这一步,有报导称Firefox也在努力兼容)。最终,测验页面中还包括隐藏的懒加载图画,各浏览器均未对其宣布恳求。

揭秘HTTP/3优先级

作为测验的收尾,我计划试试用JavaScript fetch() API宣布的恳求的优先级。在这部分,不同浏览器的体现又呈现了重大不合。Chromium以为这些恳求十分重要,而Firefox则默许将其划入“低”优先级(等同于图画,乃至是预取)。我还测验了从延期JS文件内(第二行)履行fetch() ,并猜测Chromium会给它设置较低的优先级(跟延期JS自身保持一致),但成果并非如此。这再次凸显出fetchpriority特点的重要含义,它能有效帮助Chromium完结优先级下调。

之后,我又想到覆盖掉优先级信息。究竟在新体系中,这是靠HTTP标头完结的,咱们能够在fetch()调用中设置自定义标头!不出所料,在手动宣布priority: u=0,1 这条标头后,三款浏览器又做出了互相不同的反响。

图九:不同浏览器在处理自定义优先级HTTP标头时的差异。

Chromium会一起发送Priority_update帧加自定义标头。Firefox只发送两条priority标头字段:自身,再加上来自fetch()的字段。我不敢100%确认,但我猜HTTP RFC应该不答应这种作法吧。究竟两个值是矛盾的,所以不清楚服务器应该挑选运用哪个值(在上表中,我挑选了Firefox的u=4默许值)。最终,Safari用咱们传递给fetch()的一个标头覆盖了自己的标头,这能够算是“正确”(至少符合预期)的反响。

整体而言,我对浏览器答应手动设置标头感觉有点意外。之前这个问题就在IETF上激起过评论,并且Chromium还清晰表明反对。但理论上讲,咱们能利用这项功用经过自定义优先级完结细粒度的资源加载——比方单页应用程序,应该会很风趣!

很明显,不同浏览器在某些资源类型的重要度方面存在重大定见不合。但这本来能够一致同来的,究竟其完结底子就不依赖于浏览器,而是(HTML)加载语义。从表象上看,各家浏览器厂商好像便是一拍脑袋就定下了这些规矩,底子就没有实证数据作为支撑(也没有正确记载)。好了,吐槽就到这儿,立刻进入下一环节。

04服务器差异

如咱们所见,即便是在新的和比较简略的体系傍边,不同浏览器向服务器发送优先级信号的办法和思路也有很大差别。但对服务器来说,这些都不重要:只要按浏览器发来的要求履行,尽或许照章办事就行了。但真是如此吗?

留意:操作并不总是严厉依照信号进行的。特别是在较为杂乱的设置中(比方运用CDN),某些优先级更高的资源或许暂时没有可用数据(例如没有被缓存在边际方位),这时候先发送优先级较低的资源反而更有含义。

相同,我发现还没人对现已十分成熟的HTTP/3服务器进行过优先级支撑性查询(前不久,才刚刚有人对HTTP/2服务器做过这方面查询)。靠人不如靠己,我仍是决议亲身上场。

很惋惜,得到的成果不怎样好。我测验的服务器几乎没有一个能彻底遵从哪怕是比较简略的优先级信号,并且大多数在处理更杂乱的信号组合时还出了严重问题。

但这儿我不计划把这些服务器的名号报出来,究竟这种羞辱性的展示没啥建设性。相反,我直接联系到服务器开发团队,探讨该怎么解决问题。再过半年或许一年,或许咱们能够从头回顾这个论题并分享后续进展。

以下列出的是我查询到的不良行为,各截图均来自Chromium加载的原始测验页面:

揭秘HTTP/3优先级

图十:在相同浏览器内加载同一页面时,HTTP/3服务器的不同体现。

如您所见,服务器并不总是依据浏览器的信号接纳数据,这当然会对某些Webperf目标发生影响。假如咱们现已在测验运用HTTP/3,但得到的成果达不到预期,或许原因就在这儿。

就个人而言,我很难理解为什么会存在这些问题。HTTP/2服务器之所以体现欠安,一大原因便是HTTP/2的优先级树难以正确完结。能够说,新的HTTP/3体系应该更容易做对,可咱们在所谓的成熟完结中仍是碰上了影响很大的bug……算了,懒得诉苦了

05总结

本文的中心,便是展示不同浏览器在经过HTTP加载资源的详细办法上存在巨大差异(优先级排序只是其间一例)。依据详细页面设置,您或许在不同浏览器间发现中等或较大的Webperf距离,咱们应该了解这些距离的由来以应对或许呈现的极点状况。

别的,本文还期望向咱们展示新的“优先级提示”及fetchpriority特点的含义。它们不仅能更改浏览器的默许行为,还能跨不同浏览器完结更一致的反响办法(假如Firefox和Safari乐意接纳这些新元素)。

最终,从更广泛的“软件工程”层面来讲,我发现了一个风趣的现象:再简略的体系,也不一定就能确保跨渠道间的行为一致,也不能确保仓库自规划之初就不存在bug。虽然问题多多,我仍是为自己参加新的HTTP/3体系的规划工作而自豪。我以为这是朝着正确方向迈出的一步,也期望新成果能被向下移植到HTTP/2完结傍边。

鸣谢

特别感谢Barry Pollard、Lucas Pardue和Patrick Meenan为本文编撰供给的帮助。

对“HTTP/3优先级揭秘”的四条回应

1.Web Performance Calendar The Web Performance Engineer’s Swiss Army Knife December 26th, 2022

[…] 随着HTTP/3的推出,HTTP/2安享的静好年月被打破了。Robin Marx之前曾发文评论过优先级排序中的一些细微差别 […]

2.Web Performance Calendar LCP attribution: a fetchpriority breakdown December 30th, 2022

[…] 除了经过web vitals attribution了解关于fetchpriority的信息,我还再次体会到HTTP优先级排序的确是件杂乱的工作。[…]

3.HTTP/3 Prioritization Demystified – My Blog January 3rd, 2023

[…] 了解更多 […]

4.Andrew January 3rd, 2023

“留意:这便是我反对人们总说什么H2和H3答应并行发送多个资源的理由,由于这底子就不叫真实的并行!H1反而比H2/H3并行得多,由于前者有6个独立衔接。充其量,H2/3数据只能算在线路上交织或多路复用。”

其实这跟H1并没有差异。假定客户端有一条互联网衔接,来自这6个“独立”H1衔接的数据包有必要在这条线路上交织,以便沿单一通道传输。二者的不同之处在于:H1上的序列化是由咱们无法操控的下一跳路由器在无形中完结的,而H2/H3则是经过客户端和服务器间的协作端到端完结的。