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]。
响应 MAY(可)包含多个 metric-name 相同的 server-timing-metric 项,用户代理 MUST(必须)处理并公开所有相关项。
用户代理 MAY 可以以任何顺序呈现给定的指标——即 HTTP 头字段中指标的顺序没有实际意义。
该头字段定义为可扩展语法,以便未来增加参数。用户代理对响应 Server-Timing 头字段中无法识别的 server-timing-param-name,MUST 忽略这些标记并继续处理,不得报错。
为避免产生歧义,单独的 server-timing-param-name 在 server-timing-metric 内 SHOULD
NOT(不应)出现多次。如果某个 server-timing-param-name 被指定多次,只考虑第一次出现(即使 server-timing-param
不完整或无效),后续全部 MUST 忽略,不报错也不影响 server-timing-metric 处理。仅此场景下参数顺序才有重要意义。
用户代理 MUST 忽略 server-timing-param-value 后到下一个 server-timing-param 和当前
server-timing-metric 末尾之间的多余字符。
用户代理 MUST 忽略 metric-name 后到第一个 server-timing-param 和下一个
server-timing-metric 之间的多余字符。
本规范定义 "dur"(对应 duration)和
"desc"(对应 description)参数名的
server-timing-params,均为可选项。
- 为了减少 HTTP 消耗,推荐指标名和描述尽量简短——例如采用缩写,省略非必需值。
- 由于客户端、服务器和中间实体之间无法保证时间同步,无法将有意义的
startTime映射到客户端时间线。因此,本规范故意省略startTime属性;开发者如需关联多个项,可通过指标名和/或描述传递自定义数据。 - 服务器及相关中间实体完全控制哪些指标何时传递给用户代理。例如,某些指标可能因隐私或安全原因受到限制——详见 § 4 隐私与安全。
要 解析 server-timing 头字段,给定字符串 field:
-
令 position 为 位置变量,初始指向 field开头。
-
令 name 为 收集一串码点的结果,条件是 field 中码点不等于 U+003B(;),以 position 为起点。
-
如果 name 为空字符串,返回 null。
-
令 metric 为新的
PerformanceServerTiming实例,metric name 设为 name。 -
令 params 为空 有序 map。
-
当 position 未到 field末尾:
-
前进 position 1。
-
令 paramName 为 收集一串码点结果,条件是 field 中码点不等于 U+003D(=),以 position 为起点。
-
前进 position 1。
-
令 paramValue 为空字符串。
-
跳过 field中的 ASCII 空白符,以 position 为起点。
-
若 position所指码点为 U+0022("),则:
-
paramValue 设为 收集 HTTP 引号字符串的结果(field、position,extract-value 标志打开)。
-
收集码点串,条件是 field 中码点不等于 U+003B(;),以 position 为起点。结果不使用。
-
-
否则:
-
-
返回 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,在 此对象的 timing info 的 server-timing headers 中:
-
返回 entries。
4. 隐私与安全
本节为非规范性内容。本规范定义的接口会向包含有服务器计时指标资源的页面暴露潜在敏感的应用和基础设施信息。因此,PerformanceServerTiming
接口默认受 同源策略限制。资源提供方可通过添加 Timing-Allow-Origin HTTP 响应头(详见 [RESOURCE-TIMING]),指定允许访问服务器指标的域,从而允许服务器计时信息被获取。但用户代理 MAY 仍可保留 同源的限制。
除 Timing-Allow-Origin HTTP
响应头外,服务器也可用相关逻辑控制哪些指标何时对谁返回——例如,服务器只向已认证用户提供部分指标,对其他用户一律不提供。
5. IANA 注意事项
永久消息头字段注册表应更新如下条目([RFC3864]):
5.1. Server-Timing 头字段
- 头字段名
- Server-Timing
- 适用协议
- http
- 状态
- 标准
- 作者/变更管控
- W3C
- 规范文档
- 本规范(见 Server-Timing Header Field)
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
| 名称 | 耗时 | 描述 |
|---|---|---|
| miss | ||
| db | 53 | |
| app | 47.2 | |
| customView | ||
| dc | atl | |
| cache | 23.2 | Cache Read |
| total | 123.4 |
上述头字段传递了六个不同的指标,展示了服务器如何向用户代理传递各种数据:仅指标名称、带值的指标、带值和描述的指标,以及带描述的指标。例如,上述指标可说明
example.com/resource.jpg 的抓取过程:
- 发生了一次缓存未命中。
- 请求经由 “atl” 数据中心(“dc”)路由。
- 数据库(“db”)耗时 53 毫秒。
- 缓存读取耗时 23.2 毫秒。
- 应用服务器(“app”)处理 “customView” 模板或函数耗时 47.2 毫秒。
- 请求-响应周期总耗时 123.4 毫秒,该数值记录于响应末尾并通过 trailer 字段传递。
应用可以通过提供的 JavaScript 接口收集、处理并利用这些指标:
// serverTiming 条目可存在于 'navigation' 和 'resource' 条目上 for ( const entryTypeof [ 'navigation' , 'resource' ]) { for ( const { name: url, serverTiming} of performance. getEntriesByType( entryType)) { // 遍历 serverTiming 数组 for ( const { name, duration, description} of serverTiming) { // 这里只关心慢的项 if ( duration> 200 ) { console. info( '慢的 server-timing 条目 =' , JSON. stringify({ url, entryType, name, duration, description}, null , 2 )) } } } }
7. 用例
本节为非规范性内容。7.1. 开发者工具中的服务器计时
服务器处理时间在总请求时长中可能占很大比例。例如,动态响应可能需要一次或多次数据库查询、缓存检索、API 调用、处理相关数据及渲染响应等。同样,即使是静态响应,也可能因服务器过载、缓存缓慢或其它原因被延迟。
目前,用户代理开发者工具可以显示请求何时发起、响应首字节和末字节何时到达。但开发者无法知晓服务器端具体耗时位置和原因,难以迅速诊断服务器是否存在性能瓶颈以及瓶颈在何处。要排查此问题,开发者通常需采用多种手段:查服务器日志、在响应体内嵌入性能数据(如有可能)、使用外部工具等。这样会导致性能瓶颈的定位和诊断变得困难甚至不可行。
Server Timing 定义了一套标准机制,使服务器能向客户端传递相关性能指标,客户端能将其直接显示在开发者工具中——例如请求可用服务器发送的指标进行标注,帮助深入了解响应生成过程中的耗时。
7.2. 自动分析用的服务器计时
除了在开发者工具中呈现服务器发送的性能指标外,标准 JavaScript 接口还可让分析工具自动采集、处理、回传并聚合这些指标用于运维和性能分析。
7.3. 请求路由性能衡量
Server Timing 使源服务器能传递处理请求过程中耗时位置与方式的性能指标。但同一个请求与响应也可能被一个或多个代理(如缓存服务器、负载均衡等)路由,每个代理都可能引入自身延迟,并希望提供自己的性能指标。
例如,CDN 边节点可能希望报告使用哪个数据中心、资源是否命中缓存、从缓存或源服务器获取响应所用时间等。其它代理也可重复此流程,从而获得请求路由及耗时的全链路可视化。
同样,当 Service Worker 激活时,部分或全部导航与资源请求可能会通过其路由。实际上,活跃的 Service Worker 就是本地代理,能够重定向请求、返回缓存响应、合成自定义响应等。因此,Server Timing 也让 Service Worker 可报告请求处理的自定义性能指标:比如请求从服务器或本地缓存获取,相关步骤耗时等。
8. 致谢
本节为非规范性内容。本文部分内容参考了 [NAVIGATION-TIMING]、 [RESOURCE-TIMING]、 [PERFORMANCE-TIMELINE-2] 和 [RFC6797] 规范,均已授权许可。