1. 引言
本节是非规范性的。准确地测量 Web 应用的性能特性是加速 Web 应用的关键环节。[NAVIGATION-TIMING] 和 [RESOURCE-TIMING] 为文档及其资源提供了详细的请求计时信息,包括请求发起时间以及协商连接和接收响应过程中的各项里程碑。然而,尽管用户代理能够观察到请求的计时数据,却无法洞悉请求‑响应周期中某些阶段耗时的原因——例如请求是如何路由的、服务器内部的耗时分布等。
本规范引入了 PerformanceServerTiming 接口,使服务器能够向用户代理传达请求‑响应周期的性能度量,并提供 JavaScript 接口,使应用程序能够收集、处理并依据这些度量进行优化。
2. Server-Timing 头字段
Server-Timing 头字段 用于在给定的请求‑响应周期中传递一个或多个度量及其描述。[RFC5234] 中的 ABNF(增强巴克斯‑诺尔形式)语法如下:
Server-Timing = #server-timing-metric server-timing-metric = metric-name *( OWS ";" OWS server-timing-param ) metric-name = token server-timing-param = server-timing-param-name OWS "=" OWS server-timing-param-value server-timing-param-name = token server-timing-param-value = token / quoted-string
有关 #、*、OWS、token 与 quoted-string 的定义,请参见 [RFC7230]。
响应可以包含多个拥有相同 metric-name 的 server-timing-metric 条目,用户代理 **必须** 处理并公开所有这些条目。
用户代理 **可以** 以任意顺序呈现提供的度量——即 HTTP 头字段中度量的顺序并不重要。
此头字段采用可扩展的语法,以便将来添加参数。若用户代理未识别响应中 Server-Timing 头字段的特定 server-timing-param-name,则 **必须** 忽略这些令牌并继续处理,而不是报告错误。
为避免任何可能的歧义,单个 server-timing-param-name **不应** 在同一 server-timing-metric 中出现多次。如果同一参数名出现多次,只应使用第一次出现的值,即使该 server-timing-param 不完整或无效。所有后续出现 **必须** 被忽略,且不产生错误或改变对 server-timing-metric 的处理方式。这是唯一一次参数顺序被视为重要的情况。
用户代理 **必须** 忽略位于 server-timing-param-value 之后、下一个 server-timing-param 之前以及当前 server-timing-metric 结束之前的多余字符。
用户代理 **必须** 忽略位于 metric-name 之后、首个 server-timing-param 之前以及下一个 server-timing-metric 之前的多余字符。
本规范为服务器‑计时参数定义了参数名 “dur”(对应 duration)和 “desc”(对应 description),两者均为可选。
- 为尽量降低 HTTP 开销,提供的名称和描述应尽可能简短——例如使用缩写并在可能的情况下省略可选值。
- 由于客户端、服务器及中间人的时钟同步无法得到保证,无法将有意义的
startTime映射到客户端时间轴上。因此本规范刻意省略了任何startTime属性。如果开发者希望在多个条目之间建立关联,可通过度量名称和/或描述传递自定义数据。 - 服务器和/或任何相关中间人完全控制向用户代理传递哪些度量以及何时传递。例如,出于隐私或安全原因,某些度量可能受到限制——参见§ 4 隐私与安全章节。
要 解析 server-timing 头字段,给定字符串 field
-
设 position 为一个位置变量,最初指向 field 的开头。
-
设 name 为从 field 中收集一系列码点的结果,收集过程在遇到 U+003B (;) 前停止,起始位置为 position。
-
去除 name 两端的 ASCII 空白字符。
-
如果 name 为空字符串,返回
null。 -
创建一个新的
PerformanceServerTiming实例 metric,其度量名称为 name。 -
设 params 为一个空的有序映射。
-
当 position 未到达 field 末尾时
-
将 position 前移 1。
-
设 paramName 为从 field 中收集一系列码点的结果,收集过程在遇到 U+003D (=) 前停止,起始位置为 position。
-
去除 paramName 两端的 ASCII 空白字符。
-
将 position 前移 1。
-
设 paramValue 为空字符串。
-
跳过 field 中位于 position 的 ASCII 空白字符。
-
如果 position 位置的码点 为 U+0022 ("),则
-
将 paramValue 设为从 field 在 position 处 extract-value 标志 打开的收集 HTTP 引号字符串 的结果。
-
收集一系列码点,直到遇到 U+003B (;) 为止,起始位置为 position。收集结果不予使用。
-
-
否则
-
设 rawParamValue 为从 field 中收集、直至遇到 U+003B (;) 为止的码点序列的结果,起始位置为 position。
-
设 paramValue 为对 rawParamValue 进行去除空白后的结果。
-
-
-
返回 metric。
3. PerformanceServerTiming 接口
[Exposed =(Window ,Worker )]interface {PerformanceServerTiming readonly attribute DOMString name ;readonly attribute DOMHighResTimeStamp duration ;readonly attribute DOMString description ; [Default ]object (); };toJSON
当调用 toJSON 时,执行 [WEBIDL] 中的默认 toJSON 步骤。
3.1. name 属性
name 的 getter 步骤返回 this 的度量名称。
3.2. duration 属性
duration 的 getter 步骤如下:
-
如果 dur 为错误,返回 0;否则返回 dur。
由于 duration 是一个 DOMHighResTimeStamp,它通常表示以毫秒为单位的持续时间。由于在实践中难以强制执行此约定,duration 可以使用任意时间单位,而推荐使用毫秒作为单位。
3.3. description 属性
description 的 getter 步骤返回 this 的params["desc"](若 存在),否则返回空字符串。
一个 PerformanceServerTiming 关联有一个字符串 度量名称,初始为空字符串。
一个 PerformanceServerTiming 关联有一个有序映射 params,初始为空。
3.4. 对 PerformanceResourceTiming 接口的扩展
PerformanceResourceTiming 接口(本规范对其作部分扩展)定义于 [RESOURCE-TIMING]。
[Exposed =(Window ,Worker )]partial interface PerformanceResourceTiming {readonly attribute FrozenArray <PerformanceServerTiming >serverTiming ; };
3.5. serverTiming 属性
serverTiming 的 getter 步骤如下:
-
设 entries 为一个新的列表。
-
遍历 field 于 this 的时序信息 的server‑timing 头部集合 中的每个 field
-
返回 entries。
4. 隐私与安全
本节是非规范性的。本规范中定义的接口会向任何包含了宣告 Server Timing 度量的资源的网页暴露可能敏感的应用与基础设施信息。因此,默认情况下,PerformanceServerTiming 接口的访问受到同源策略的限制。资源提供者可以通过添加 Timing-Allow-Origin HTTP 响应头(如[RESOURCE-TIMING] 中所定义)来显式允许服务器计时信息被访问,指定哪些域可访问服务器度量;但用户代理 **仍可** 保持同源策略的限制。
除了使用 Timing-Allow-Origin 响应头之外,服务器还可以使用相应的逻辑控制返回哪些度量、何时返回以及返回给谁——例如,仅向已正确认证的用户提供特定度量,对其他所有用户则不返回任何信息。
5. IANA 考量
应当使用以下登记([RFC3864])更新永久消息头字段注册表:
5.1. Server-Timing 头字段
- 头字段名称
- Server-Timing
- 适用协议
- http
- 状态
- 标准
- 作者/变更控制者
- W3C
- 规范文档
- 本规范(参见 Server-Timing 头字段)
6. 示例
本节是非规范性的。> GET /resource HTTP/1.1 > Host: example.com < HTTP/1.1 200 OK < Server-Timing: miss, db;dur=53, app;dur=47.2 < Server-Timing: customView, dc;desc=atl < Server-Timing: cache;desc="Cache Read";dur=23.2 < Trailer: Server-Timing < (... snip response body ...) < Server-Timing: total;dur=123.4
| 名称 | 持续时间 (Duration) | 描述 |
|---|---|---|
| miss | ||
| db | 53 | |
| app | 47.2 | |
| customView | ||
| dc | atl | |
| 缓存 | 23.2 | Cache Read |
| 全序 | 123.4 |
上述头字段传递了六个不同的度量,展示了服务器向用户代理传递数据的所有可能方式:仅度量名称、度量加数值、度量加数值与描述、以及度量加描述。例如,上面的度量可用于指示针对 example.com/resource.jpg 的抓取:
- 出现了缓存未命中。
- 请求被路由至 “atl” 数据中心(“dc”)。
- 数据库(“db”)耗时 53 ms。
- 一次缓存读取耗时 23.2 ms。
- 应用服务器(“app”)耗时 47.2 ms 处理 “customView” 模板或函数。
- 服务器端的整个请求‑响应周期耗时 123.4 ms,此值在响应结束时记录并通过 trailer 字段传递。
应用程序可通过提供的 JavaScript 接口收集、处理并依据这些度量采取相应操作。
// serverTiming entries can live on 'navigation' and 'resource' entries for ( const entryTypeof [ 'navigation' , 'resource' ]) { for ( const { name: url, serverTiming} of performance. getEntriesByType( entryType)) { // iterate over the serverTiming array for ( const { name, duration, description} of serverTiming) { // we only care about "slow" ones if ( duration> 200 ) { console. info( 'Slow server-timing entry =' , JSON. stringify({ url, entryType, name, duration, description}, null , 2 )) } } } }
7. 使用场景
本节是非规范性的。7.1. 开发者工具中的 Server Timing
服务器处理时间往往占总请求时间的相当大比例。例如,动态响应可能需要一次或多次数据库查询、缓存查找、API 调用、数据处理与渲染等步骤。即便是静态响应,也可能因服务器过载、缓存慢或其他原因被延迟。
目前,用户代理的开发者工具只能显示请求的发起时间以及收到的首字节和末字节的时间。然而,它们无法展示服务器端的耗时分布,这导致开发者难以及时判断服务器是否存在性能瓶颈以及瓶颈位于哪个组件。为了解答此类问题,开发者只能依赖多种手段:检查服务器日志、在响应中嵌入性能数据(若可能)、使用外部工具等。这使得定位与诊断性能瓶颈既困难又在很多情况下不切实际。
Server Timing 定义了一套标准机制,使服务器能够将相关性能度量发送给客户端,并允许客户端在开发者工具中直接展示这些度量——例如,请求可以被标注上服务器发送的度量,以便洞察生成响应时的时间消耗位置与原因。
7.2. 用于自动化分析的 Server Timing
除了在开发者工具中展示服务器端的性能度量之外,标准的 JavaScript 接口还能让分析工具自动收集、处理、上报并聚合这些度量,以用于运营及性能分析。
7.3. 测量请求路由性能
Server Timing 使源服务器能够报告请求在处理期间时间的消耗位置与方式。但同一请求与响应也可能经由一个或多个代理(如缓存服务器、负载均衡器等)转发,每个代理都可能引入自身的延迟,并希望提供关于时间消耗的性能度量。
例如,CDN 边缘节点可能需要报告所使用的数据中心、资源是否命中缓存,以及从缓存或源服务器检索响应所耗时间。此外,其他代理也可能进行相同的报告,从而实现对请求路由全过程以及时间消耗位置的完整可视化。
同理,当 Service Worker 处于激活状态时,部分或全部导航与资源请求可能经由它转发。实际上,激活的 Service Worker 相当于一个本地代理,能够重新路由请求、提供缓存响应、合成响应等。因此,Server Timing 使 Service Worker 能够报告自定义的性能度量,说明请求是如何被处理的:是否从服务器抓取、是否从本地缓存提供、相关处理步骤的耗时等。
8. 致谢
本节是非规范性的。本文档重用了来自 [NAVIGATION-TIMING]、[RESOURCE-TIMING]、[PERFORMANCE-TIMELINE-2] 与 [RFC6797] 规范的文本,遵循这些规范的许可协议进行使用。