网络错误日志记录

W3C 规范草案

关于本文件的更多信息
本版本:
https://www.w3.org/TR/2025/WD-network-error-logging-20250505/
最新发布版本:
https://www.w3.org/TR/network-error-logging/
最新编辑草稿:
https://w3c.github.io/network-error-logging/
历史记录:
https://www.w3.org/standards/history/network-error-logging/
提交历史
编辑:
Douglas Creager (GitHub)
Ian Clelland (Google)
历任编辑:
Ilya Grigorik (Google) - 截止至
Julia Tuttle (Google) - 截止至
Arvind Jain (Google) - 截止至
Alois Reitbauer (Compuware Corp.) - 截止至
Jatinder Mann (Microsoft) - 截止至
反馈:
GitHub w3c/network-error-logging (拉取请求, 新建问题, 所有问题)

摘要

本文档定义了一种机制,使开发者能够为网页应用声明网络错误报告策略。用户代理可以利用该策略报告其在获取所请求资源时遇到的网络错误,这些错误阻止了请求的成功获取。

本文档状态

本节描述了本文档在发布时的状态。当前 W3C 的出版物列表和本技术报告的最新修订版可在 W3C 标准与草案索引中找到, 地址:https://www.w3.org/TR/。

本文档由 Web 性能工作组 作为 推荐标准流程 的工作草案出版。

作为工作草案发布并不代表 W3C 及其成员的认可。

本文为草案文档,可能随时被更新、替换或废弃。引用本文档应仅作为工作进展,不应作其他用途。

本文档由一在 W3C 专利政策 下运作的小组产生。 W3C 维护了 与本小组成果相关的专利公开的公开列表, 该页面同时还包含专利披露说明。若个人实际知晓某专利,且认为其包含 必要声明, 必须依照 W3C 专利政策第6节披露此信息。

本文档受 2023年11月3日 W3C 流程文件管理。

1. 引言

精准衡量网页应用的性能特性对于帮助网站开发者理解如何改进他们的网页应用至关重要。最糟糕的情况是,由于网络错误导致应用或某个特定资源加载失败。要解决此类故障,开发者需要用户代理协助,识别此类失败何时、何地以及为何发生。

目前,应用开发者无法获取来自终端用户的网页应用可用性实时数据。例如,如果用户因 DNS 查询失败、连接超时、连接被重置或其他原因导致页面加载失败,站点开发者无法检测和解决此问题。请注意,这类网络错误无法仅通过服务端检测,因为按定义,客户端可能根本无法成功与服务器建立连接。

现有方法(如合成监控)通过在预定的地理位置布置监控节点来提供部分解决方案,但需要额外的基础设施投入,且无法为真实终端用户提供真正全球范围内且接近实时的可用性数据。

网络错误日志(NEL)通过定义一种机制来满足这一需求,使网页应用能够声明一种报告策略,用户代理可据此为给定的源点上报网络错误。网页应用通过返回包含 NEL HTTP 响应头字段选择使用 NEL,该头描述期望的 NEL 策略。该策略指示用户代理记录对此源点的请求信息,并尝试将这些信息发送到此前使用 Reporting API 配置的终端组。如其名所示,NEL 报告主要用于描述错误。但为了统计不同客户端群体的错误比率,也必须了解有多少成功的请求发生;这些成功请求也可通过 NEL 机制进行报告。

例如,如果用户代理因 TCP 连接被中断无法从 https://www.example.com 获取资源,用户代理会通过 Reporting API 队列如下报告:

type
"network-error"
endpoint group
report_to 字段配置的终端组
data
{
  "referrer": "https://referrer.com/",
  "sampling_fraction": 1.0,
  "server_ip": "192.0.2.42",
  "protocol": "http/1.1",
  "elapsed_time": 321,
  "phase": "connection",
  "type": "tcp.aborted"
}

有关报告中各字段及其格式说明,请参见 5.4 生成网络错误报告;有关 NEL 注册和上报流程的更多实例,请参见 7. 示例

2. 一致性

本规范中除标为非规范性外,所有作者指南、图示、示例和注释均为非规范性内容。规范中其余部分均为规范性内容。

本文档中的关键词 MAYMUSTMUST NOTOPTIONALREQUIREDSHOULD, 其解释见 BCP 14 [RFC2119] [RFC8174] ,仅当全部大写出现时按上述含义解释。

以祈使句出现的规范性要求(如“去除所有前导空格”或“返回 false 并终止这些步骤”)应与引导算法时所用的关键词(如“must”、“should”、“may”等)对应理解。

部分一致性要求针对属性、方法或对象提出,此类要求应理解为针对用户代理的要求。

以算法或具体步骤方式表述的一致性要求,可以采用任何实现方式,只要最终效果等价。(本规范给出的算法旨在易于理解而非追求性能。)

3. 概念

3.1 网络请求

用户代理尝试通过网络为某个请求进行HTTP 网络获取资源时,就会发生一次网络请求

如果已知用户代理处于离线状态(即 navigator. onLine 返回 false),请求 不得产生 网络请求

如果请求因 混合内容CORS 失败被阻止,该 请求 不得产生 网络请求。任何 CORS 预检请求 必须产生它自己的 网络请求

对于按照 [FETCH] 标准服务请求的用户代理,一次 网络请求对应于一次 HTTP 网络获取 算法的执行。

无论采用哪种 fetch 算法和底层应用、传输协议,服务一次 网络请求均包含以下几个阶段

  1. DNS 解析:用户代理使用域名系统 [RFC1034] 将域名解析为可为该域服务 HTTP 请求IP 地址服务器
  2. 安全连接建立:用户代理与 服务器 建立连接,并在此连接上建立安全通道。
  3. 请求和响应传输:安全通道建立后,用户代理可以发送 HTTP 请求,并从 服务器 接收 响应

唯一必需的阶段请求和响应传输;其他阶段对于每个 网络请求可能不是必需的。例如,用户代理本地可以缓存 DNS 结果,从而免除后续对同一域名的 DNS 解析。同样,HTTP 持久连接允许打开一个连接后为同一分区密钥的多个请求复用。但如果多个阶段依次发生,其顺序应如上。

编者注

我们希望将这些 阶段的定义移至 [FETCH], 以便于复用。

如果用户代理能够从服务器接收有效 HTTP 响应,且该响应状态码不是 4xx5xx,则此 网络请求被认为是成功的。

如果未被判定为 成功,则该 网络请求失败

注意:HTTP 错误响应(即,状态码为 4xx5xx)会被视为失败,因此会受 NEL 策略失败采样率成功采样率控制。

3.2 网络错误

导致某 网络请求失败的错误状态被称为网络错误

每个 网络错误都有一个类型,为字符串。

每个 网络错误都有一个 阶段,用于描述错误发生在哪一阶段

dns
错误发生在 DNS 解析阶段
connection
错误发生在 安全连接建立阶段
application
错误发生在 请求和响应传输阶段

6. 预定义网络错误类型中定义了若干预定义的 网络错误类型

3.3 网络错误报告

网络错误报告 是一个 Reporting API 报告,用于描述 一个 网络错误

网络错误报告报告类型network-error

网络错误报告 不会 暴露给 ReportingObserver

网络错误报告不会暴露给 ReportingObserver, 因为报告只应由接收请求的服务器管理员或所有者可见。如果它们对 ReportingObserver 可见, 那么报告也将对发起者可见。对于跨源请求,这可能会泄露有关服务器网络配置的信息给其无法控制的第三方。

3.4 NEL 策略

NEL 策略 用于指示用户代理是否要收集发往某个 网络请求origin 的报告,如果需要,则指定报告发送目的地。 NEL 策略 通过 HTTP 响应头 下发给用户代理。

每个 NEL 策略都有一个接收 IP 地址,即用户代理收到 NEL 策略时服务器的 IP 地址

每个 NEL 策略有一个源点

每个 NEL 策略有一个 子域 标志,值可为 includeexclude

每个 NEL 策略有一个请求头列表(policy request headers) 和响应头列表(policy response headers),每个列表中的元素均为头名称

每个 NEL 策略有一个 报告组,即报告将发送到的 Reporting 终端组名称。

每个 NEL 策略有一个 ttl,表示该策略有效的秒数。

每个 NEL 策略有一个 创建时间,即用户代理收到策略的时间戳。

当从创建时间挂钟当前不安全时间 时长大于 172800 秒(48 小时)时,NEL 策略过时

当从创建时间挂钟当前不安全时间 时长大于其 ttl 时,NEL 策略已过期

3.5 采样率

期望服务大量流量的源点可能无法承载为其所有 网络请求生成的完整 NEL 报告。源点可定义采样率限制每个用户代理上报的 NEL 报告数量。由于成功请求通常远多于失败请求,源点可为不同情形指定不同的采样率。

每个 NEL 策略有一个 成功采样率,值在 0.0–1.0 之间。

每个 NEL 策略有一个 失败采样率,值在 0.0–1.0 之间。

3.6 策略缓存

合规的用户代理必须提供一个策略缓存,即用于保存 NEL 策略 的存储机制,键为 (网络分区密钥, 源点) 二元组。

此存储机制为不透明、厂商自定义且不暴露给 Web,但必须提供下列方法,供本文档定义的算法使用:

4. 策略传递

服务器可以通过 NEL 策略 以及 NEL HTTP 响应头为其管理的 origin 定义相应策略。

4.1 NEL 响应头

NEL 响应头用于将 originNEL 策略传递给用户代理。NEL 头的 ABNF(扩展巴科斯-诺尔范式)[RFC5234] 语法如下:

NEL = json-field-value

该响应头的值按照 json-field-value 的定义作为一个 JSON 对象数组来解释。数组中的每个对象为该 origin 定义一个 NEL 策略。用户代理必须只处理数组中的第一个有效策略,忽略数组中的其余策略。

用户代理必须忽略任何未知或无效的字段或值,凡不符合本规范定义语法的均忽略。一个有效的NEL响应头必须至少包含一个符合本规范所有 “必需” 字段的对象。

为防止通过脚本攻击劫持错误上报,对于通过 meta 元素指定的 NEL头,用户代理必须忽略。NEL 策略必须通过NEL 响应头下发。

meta 元素的限制与 [CSP] 规范保持一致,后者同样因安全理由只允许通过 HTTP 响应头注册上报。

4.1.1 report_to 字段

report_to 字段指定本 终端组(endpoint group),NEL 报告将被发送到此组。 注册 NEL 策略必须包含该字段;若仅为移除先前注册,可可选,详见 max_age。如存在,此字段值必须为字符串;其它类型则解析失败。

为提升 NEL 报告投递成功率,服务器应将 report_to 设为包含至少一个位于独立(不与资源 origin 共用基础设施)的 alternative origin 的 终端组;否则当资源 origin 自身出现网络故障时报告可能无法发送。另外建议配置多个端点做备用。

4.1.2 max_age 字段

必需字段 max_age 指定本 NEL 策略的有效期(以秒为单位的非负整数)。其值必须为非负整数,否则解析失败。

0 表示应将该 origin 的所有 NEL 策略从 策略缓存中移除。

为确保 NEL 报告被交付,服务器还应确保相关 Reporting 终端组max_age 配置也充分长。若 Reporting 策略过期,NEL 报告也无法送达,即使 NEL 策略本身仍然有效。

4.1.3 include_subdomains 字段

可选字段 include_subdomains 是布尔值,用于开启本 NEL 策略对该 origin 的所有子域生效(无限深度)。若对象中不存在该字段,或其值不为 true,则此策略不作用于子域。

若需 NEL 报告能覆盖子域,应确保 Reporting 终端组也配置了 include_subdomains 为 true。否则,若 Reporting 策略未覆盖该子域,NEL 报告即使策略开启对该子域,也无法送达。

4.1.4 success_fraction 字段

可选字段 success_fraction 定义对本 origin 的 成功 网络请求 的 NEL 报告采样率。 若值存在,必须为 [0.0, 1.0] 区间内的数字;否则解析失败。 若未配置,则用户代理不会采集成功网络请求的 NEL 报告。

4.1.5 failure_fraction 字段

可选字段 failure_fraction 定义对本 origin 失败 网络请求 的 NEL 报告采样率。若值存在,必须为 [0.0, 1.0] 区间内的数字,否则解析失败。 若未配置,用户代理将对所有失败网络请求都采集 NEL 报告。

4.1.6 request_headers 字段

可选字段 request_headers 定义哪些 请求头名称 将被包含在本 origin 的 网络错误报告中。值如存在必须为字符串数组。

4.1.7 response_headers 字段

可选字段 response_headers 定义哪些 响应头名称 将被包含在本 origin 的 网络错误报告中。值如存在必须为字符串数组。

4.2 处理策略头

给定一个 网络请求request)及其对应的 响应response),本算法将从中提取 NEL 策略,并据此更新 策略缓存

  1. 若满足以下任一条件,则终止:
  2. originrequestorigin
  3. key确定网络分区密钥算法在 requestreserved client 上的结果。
  4. header 为名为 NEL响应头的值。
  5. list 为 [HTTP-JFV] 4 节算法在 header 上的结果。若出错或结果为空则终止。
  6. itemlist 的第一个元素。
  7. item 未包含 max_age 字段,或该字段值不是数字,则终止。
  8. itemmax_age 字段值为 0,则移除 策略缓存originorigin 的任何 NEL 策略,并跳过余下步骤。
  9. item 未包含 report_to 字段,或该字段不是字符串,则终止。
  10. item 含有 success_fraction 字段,且其值不在 [0.0, 1.0] 范围,则终止。
  11. item 含有 failure_fraction 字段,且其值不在 [0.0, 1.0] 范围,则终止。
  12. item 含有 request_headers 字段,其值非数组或数组中有非字符串元素,则终止。
  13. item 含有 response_headers 字段,其值非数组或数组中有非字符串元素,则终止。
  14. policy 为新建的 NEL 策略,其属性如下:

    接收 IP 地址
    用户代理收到 response服务器 IP 地址
    编者注

    [FETCH] 中需要更明确地传递。

    origin
    origin
    子域 标志
    iteminclude_subdomains 且值为 true,则为 include,否则为 exclude
    请求头
    itemrequest_headers 字段值
    响应头
    itemresponse_headers 字段值
    报告组
    itemreport_to 字段值
    ttl
    itemmax_age 字段值
    创建时间
    挂钟当前不安全时间
    成功采样率
    itemsuccess_fraction 字段值,如无则为 0.0
    失败采样率
    itemfailure_fraction 字段值,如无则为 1.0
  15. 策略缓存中已存在 (key, origin) 项,则以 policy 替换;否则插入 policy 到 (key, origin)。

5. 报告传递

5.1 为请求选择策略

给定一个 网络请求request),本算法用于确定在 策略缓存中,应为该 网络请求生成报告采用哪个 NEL 策略

  1. originrequestorigin
  2. key 为对 requestreserved client 执行“确定网络分区密钥”算法的结果。
  3. 如果 (key, origin) 项存在于 策略缓存中:
    1. policy 为该项。
    2. 如果 policy 没有 过期,则返回它。
  4. 对于 origin 的每个 父origin(即 超级域匹配):
    1. 如果 (key, parent origin) 项存在于 策略缓存中:
      1. policy 为该项。
      2. 如果 policy 没有 过期 且其 subdomains 标志为 include,则返回它。
  5. 返回 无策略

5.2 提取请求头

给定一个 网络请求request)和 NEL 策略policy),本算法根据策略指示从请求中提取头部值。

  1. headers 为新建的空 ECMAScript 对象。
  2. 对于 policy请求头 列表中的每个 header name
    1. 如果 request头部列表包含 header name,则跳至下一个 header name
    2. values 为空 ECMAScript 列表。
    3. 对于 request头部列表名称header name 的每个 header,将 header加入 values
    4. headers 上新增属性,其名为 header name,值为 values
  3. 返回 headers

5.3 提取响应头

给定 响应response)和 NEL 策略policy),本算法按照策略指示从响应中提取头部值。

  1. headers 为新建的空 ECMAScript 对象。
  2. 对于 policy响应头 列表中的每个 header name
    1. 如果 response头部列表包含 header name,则跳至下一个 header name
    2. values 为空 ECMAScript 列表。
    3. response头部列表中名称为 header name 的每个 header,将 header加入 values
    4. headers 上新增属性,其名为 header name,值为 values
  3. 返回 headers

5.4 生成网络错误报告

给定 网络请求request)及其对应的 响应response),如果有匹配的 NEL 策略要求报告,则本算法为 request 生成报告并返回报告及 NEL 策略,否则返回 null。

  1. 如果对 requestorigin 是否可信算法结果不是 Potentially Trustworthy,返回 null。
  2. originrequestorigin
  3. policy 为对 request 调用5.1 为请求选择策略的结果。若 policy无策略,返回 null。
  4. 确定本次请求的有效采样率:
  5. 判断本次请求是否应上报。令 roll 为 0.0~1.0 间的随机数。如 roll > sampling rate,返回 null。
  6. report body 为新 ECMAScript 对象,包含以下属性:[ECMA-262]
    sampling_fraction
    sampling rate
    elapsed_time
    从资源拉取开始到用户代理完成或中止所用的时间(毫秒数)。
    phase
    request 失败,则为其 阶段;如果 request 成功,则为 "application"
    type
    request 失败,则为其 类型;若 request 成功,为 "ok"
  7. 如果 report bodyphase 属性不是 dns,则附加以下属性:
    server_ip
    用户代理实际发送请求到的服务器的 IP 地址(如有);否则为空字符串。
    • IPv4 用点分十进制表示。
    • IPv6 用 x:x:x:x:x:x:x:x 形式表示。
    protocol
    拉取资源使用的网络协议(以 ALPN Protocol ID 标识,如有);否则为 ""
  8. 如果 report bodyphase 不是 dnsconnection,则附加以下属性:
    referrer
    referrer policy 以及 client 决定的 request 的 referrer。
    method
    request请求方法
    request_headers
    requestpolicy 执行5.2 提取请求头的结果。
    response_headers
    responsepolicy 执行5.3 提取响应头的结果。
    status_code
    HTTP 响应的状态码(如有);否则为 0
  9. 如果 origin 不等于 policyorigin,且 policysubdomains 标志为 include,并且 report bodyphase 不是 dns, 返回 null。

    此步骤确保子域NEL 策略只能用于在策略源点的子域上的DNS 解析阶段生成报告。详情见9. 隐私注意事项

  10. 如果 report bodyphase 不是 dns,且 report bodyserver_ip 属性非空且不等于 policy接收 IP 地址
    1. report bodyphase 设为 dns
    2. report bodytype 设为 dns.address_changed
    3. 清空 report bodyrequest_headersresponse_headersstatus_codeelapsed_time 属性。
    4. 断言:report body 内所有在 DNS 解析期间不可用的信息都已被清除。

    当服务器与策略的 IP 地址不匹配时,此步骤会“降级”NEL 报告,仅允许其携带 DNS 解析相关信息。这样做目的是隐私保护,确保最后只有域名持有者能收到该报告,否则不可追踪到具体服务器。详见 9. 隐私注意事项7.5 多 IP 地址源站

  11. 如果 policy 已经 过时,则从 策略缓存中删除 policy
  12. 返回 report bodypolicy

5.5 投递网络报告

给定一份 ECMAScript 对象(report body,通常由 生成网络错误报告返回,随后由调用规范补充)及其匹配的 NEL 策略policy)和 网络请求request),本算法负责队列化报告以进行投递。

  1. urlrequest 的 URL。

  2. 移除 url片段

  3. 如果 report bodyphase 属性为 dnsconnection

    1. 移除 url路径查询

  4. 以如下参数生成一个网络报告

    type
    network-error
    data
    report body
    endpoint group
    policy报告组
    url
    url 执行 URL 序列化器 的结果。

6. 预定义网络错误类型

本节定义了若干预定义的网络错误类型

用户代理可以扩展该列表,添加自定义网络错误 类型,例如为新协议或现有协议的更详细错误描述增加支持。此时,用户代理应当遵循点分命名([group].[optional-subgroup].[error-name])格式,以便对错误报告进行一致和简化的处理——比如收集器可以按类别/子组聚合展示。

6.1 DNS 解析错误

本节内所有网络错误均发生在DNS 解析阶段,故其phasedns

dns.unreachable
DNS 服务器不可达
dns.name_not_resolved
DNS 服务器有响应,但无法解析该地址
dns.failed
由于非上述原因,请求 DNS 服务器失败
dns.address_changed
指定请求的 origin 的 IP 地址自对应NEL 策略下发以来发生了变更

6.2 安全连接建立错误

本节内所有网络错误均发生在安全连接建立阶段,故其phaseconnection

tcp.timed_out
与服务器的 TCP 连接超时
tcp.closed
TCP 连接被服务器关闭
tcp.reset
TCP 连接被重置
tcp.refused
服务器拒绝建立 TCP 连接
tcp.aborted
TCP 连接被中止
tcp.address_invalid
IP 地址无效
tcp.address_unreachable
IP 地址不可达
tcp.failed
由于非上述原因,TCP 连接失败
tls.version_or_cipher_mismatch
TLS 连接因协议版本或加密套件不匹配而中止
tls.bad_client_auth_cert
TLS 连接因客户端证书无效而中止
tls.cert.name_invalid
TLS 连接因证书名称无效而中止
tls.cert.date_invalid
TLS 连接因证书日期无效而中止
tls.cert.authority_invalid
TLS 连接因签发机构无效而中止
tls.cert.invalid
TLS 连接因证书无效而中止
tls.cert.revoked
TLS 连接因服务器证书已吊销而中止
tls.cert.pinned_key_not_in_cert_chain
TLS 连接因密钥钉扎错误而中止
tls.protocol.error
TLS 连接因 TLS 协议错误而中止
tls.failed
由于非上述原因,TLS 连接失败

6.3 请求和响应传输错误

本节内所有网络错误均发生在请求和响应传输阶段,故其phaseapplication

http.error
用户代理成功接收到响应,但其状态码为 4xx5xx
http.protocol.error
因 HTTP 协议错误导致连接中止
http.response.invalid
响应为空、长度不符、编码不合法等导致用户代理无法处理响应的情况
http.response.redirect_loop
因检测到重定向循环请求被中止
http.failed
因协议错误导致连接失败(不属于上面的具体情况)
abandoned
用户在请求完成前中止了资源拉取
unknown
错误类型未知

7. 示例

7.1 策略定义示例

> GET / HTTP/1.1
> Host: example.com

< HTTP/1.1 200 OK
< ...
< Report-To: {"group": "network-errors", "max_age": 2592000,
              "endpoints": [{"url": "https://example.com/upload-reports"}]}
< NEL: {"report_to": "network-errors", "max_age": 2592000}

NEL 响应头定义了一个 NEL 策略,指示用户代理将关于 example.com 的网络错误报告发送到名为 network-errors终端组。该策略有效期为 2592000 秒(30 天)。

注意,上述注册仅当响应来自于潜在可信源点时才会成功。

> GET / HTTP/1.1
> Host: example.com

< HTTP/1.1 200 OK
< ...
< NEL: {"max_age": 0}

NEL 响应头指示用户代理移除 example.com 的所有现有 NEL 策略

7.2 网络错误报告示例

本节包含 网络错误报告的示例,当用户代理在某个已经注册了NEL 策略origin 上遇到网络错误时,用户代理可能会队列化这些报告。我们展示了使用 [REPORTING] API 上传时生成的完整报告载荷;载荷的 body 字段包含 网络错误报告体

{
  "age": 0,
  "type": "network-error",
  "url": "https://www.example.com/",
  "body": {
    "sampling_fraction": 0.5,
    "referrer": "http://example.com/",
    "server_ip": "2001:DB8:0:0:0:0:0:42",
    "protocol": "h2",
    "method": "GET",
    "request_headers": {},
    "response_headers": {},
    "status_code": 200,
    "elapsed_time": 823,
    "phase": "application",
    "type": "http.protocol.error"
  }
}

该报告表示用户代理尝试从 example.com 导航到 www.example.com,其成功解析为 2001:DB8::42。但虽然用户代理从服务器通过 HTTP/2(h2)协议接收到 200 响应,却在过程中遇到协议错误,最终放弃导航。用户代理在导航开始 823 毫秒后终止导航。最后,在网络错误发生后立即发送本报告,即报告 age 为 0。

{
  "age": 0,
  "type": "network-error",
  "url": "https://widget.com/thing.js",
  "body": {
    "sampling_fraction": 1.0,
    "referrer": "https://www.example.com/",
    "server_ip": "",
    "protocol": "",
    "method": "GET",
    "request_headers": {},
    "response_headers": {},
    "status_code": 0,
    "elapsed_time": 143,
    "phase": "dns",
    "type": "dns.name_not_resolved"
  }
}

上述报告表示用户代理尝试从 https://www.example.com/ 获取 https://widget.com/thing.js,但无法解析 DNS 名称(widget.com),请求在 143 毫秒后被用户代理中止。因先前请求 widget.com 时曾下发有效 NEL 策略,本次请求产生了 网络错误报告。报告上传于错误发生后立即,即 age 为 0。

7.3 DNS 配置错误

> GET / HTTP/1.1
> Host: example.com

< HTTP/1.1 200 OK
< ...
< Report-To: {"group": "network-errors", "max_age": 2592000,
              "endpoints": [{"url": "https://example.com/upload-reports"}]}
< NEL: {"report_to": "network-errors", "max_age": 2592000, "include_subdomains": true}

NEL 响应头允许 example.com 的所有者检测 DNS 配置错误——例如遗忘添加解析 new-subdomain.example.com 到 IP 地址的新资源记录。当用户代理尝试请求 new-subdomain.example.com 时,可能会生成如下报告:

{
  "age": 0,
  "type": "network-error",
  "url": "https://new-subdomain.example.com/",
  "body": {
    "sampling_fraction": 1.0,
    "server_ip": "",
    "protocol": "http/1.1",
    "method": "GET",
    "request_headers": {},
    "response_headers": {},
    "status_code": 0,
    "elapsed_time": 48,
    "phase": "dns",
    "type": "dns.name_not_resolved"
  }
}

7.4 缓存校验监控

> GET / HTTP/1.1
> Host: example.com

< HTTP/1.1 200 OK
< ...
< Report-To: {"group": "network-errors", "max_age": 2592000,
              "endpoints": [{"url": "https://example.com/upload-reports"}]}
< NEL: {"report_to": "network-errors", "max_age": 2592000, "success_fraction": 1.0,
        "request_headers": ["If-None-Match"], "response_headers": ["ETag"]}
< ETag: 01234abcd

在本例中,example.com 的所有者使用 ETag 响应头标识对应资源的不同版本。用户代理随后可使用 If-None-Match 请求头告知服务器客户端已缓存的资源版本,从而在副本有效时避免生成和下发内容。

通过让该域名的 NEL 响应头包含 request_headersresponse_headers 字段,浏览器将在 NEL 报告中包含 If-None-Match 请求头和 ETag 响应头的副本,从而让站点所有者追踪缓存策略效果。

基于上述,考虑以下事件流程:

  1. 用户代理向 example.com 发送 请求,收到 成功响应,响应中含有 ETag。用户代理将生成如下 NEL 报告:

    {
      "age": 0,
      "type": "network-error",
      "url": "https://example.com/",
      "body": {
        "sampling_fraction": 1.0,
        "server_ip": "192.0.2.1",
        "protocol": "http/1.1",
        "method": "GET",
        "request_headers": {},
        "response_headers": {
          "ETag": ["01234abcd"]
        },
        "status_code": 200,
        "elapsed_time": 1392,
        "phase": "application",
        "type": "ok"
      }
    }
  2. 之后用户代理又发送一次 请求,本地仍有原始资源副本,并在 If-None-Match 请求头中添加其版本。服务器检查该版本仍然有效,并返回 304 响应,表示缓存依然有效。用户代理将生成如下报告:

    {
      "age": 0,
      "type": "network-error",
      "url": "https://example.com/",
      "body": {
        "sampling_fraction": 1.0,
        "server_ip": "192.0.2.1",
        "protocol": "http/1.1",
        "method": "GET",
        "request_headers": {
          "If-None-Match": ["01234abcd"]
        },
        "response_headers": {
          "ETag": ["01234abcd"]
        },
        "status_code": 304,
        "elapsed_time": 45,
        "phase": "application",
        "type": "ok"
      }
    }
  3. 晚些时候,用户代理再次向 example.com 发送 请求,缓存依然,与前例一样带了 If-None-Match,但服务器此时检测到有新版本,生成并下发新内容及新的 ETag,用户代理生成如下报告:

    {
      "age": 0,
      "type": "network-error",
      "url": "https://example.com/",
      "body": {
        "sampling_fraction": 1.0,
        "server_ip": "192.0.2.1",
        "protocol": "http/1.1",
        "method": "GET",
        "request_headers": {
          "If-None-Match": ["01234abcd"]
        },
        "response_headers": {
          "ETag": ["56789ef01"]
        },
        "status_code": 200,
        "elapsed_time": 935,
        "phase": "application",
        "type": "ok"
      }
    }

7.5 多 IP 地址的源站

对于 origin域名解析为多个 IP 地址的情况,NEL 有时会“降级”错误报告,仅保留更少的出错信息,因为用户代理无法确认 origin 的拥有者和实际 服务器的拥有者一致。

例如,假设 example.com 由三个不同 IP 地址的 服务器 负责,服务端将 DNS 解析为 192.0.2.1192.0.2.2192.0.2.3,并依靠用户代理对其请求进行负载均衡。服务所有者会下发如下 NEL 策略

> GET / HTTP/1.1
> Host: example.com

< HTTP/1.1 200 OK
< ...
< Report-To: {"group": "network-errors", "max_age": 2592000,
              "endpoints": [{"url": "https://example.com/upload-reports"}]}
< NEL: {"report_to": "network-errors", "max_age": 2592000,
        "success_fraction": 1.0, "failure_fraction": 1.0}

基于上述,考虑如下事件序列:

  1. 用户代理向 192.0.2.1 发送 请求,收到 成功响应。响应中也包含上述 NEL 策略,用户代理将策略的 接收 IP 地址 设为 192.0.2.1。由于接收 IP 地址与服务器 IP 地址匹配(成功请求必然如此),生成如下 NEL 报告:

    {
      "age": 0,
      "type": "network-error",
      "url": "https://example.com/",
      "body": {
        "sampling_fraction": 1.0,
        "server_ip": "192.0.2.1",
        "protocol": "http/1.1",
        "method": "GET",
        "request_headers": {},
        "response_headers": {},
        "status_code": 200,
        "elapsed_time": 57,
        "phase": "application",
        "type": "ok"
      }
    }
  2. 用户代理再向 192.0.2.2 发送请求收到成功响应,此响应同样包含 NEL 策略,用户代理将策略的 接收 IP 地址 更新为 192.0.2.2,由于匹配,生成如下报告:

    {
      "age": 0,
      "type": "network-error",
      "url": "https://example.com/",
      "body": {
        "sampling_fraction": 1.0,
        "server_ip": "192.0.2.2",
        "protocol": "http/1.1",
        "method": "GET",
        "request_headers": {},
        "response_headers": {},
        "status_code": 200,
        "elapsed_time": 34,
        "phase": "application",
        "type": "ok"
      }
    }
  3. 用户代理再尝试向 192.0.2.3 发送请求,但无法建立连接。此时策略仍在 策略缓存中,理想状况下用户代理会生成 about 失败 网络请求tcp.timed_out 报告。但实际上由于策略中的“接收 IP 地址” (192.0.2.2) 与本次请求实际的服务器 IP 不符,用户代理无法确认 192.0.2.3 是否归属于 example.com 拥有者,因此必须降级为 dns.address_changed

    {
      "age": 0,
      "type": "network-error",
      "url": "https://example.com/",
      "body": {
        "sampling_fraction": 1.0,
        "server_ip": "192.0.2.3",
        "protocol": "http/1.1",
        "method": "GET",
        "request_headers": {},
        "response_headers": {},
        "status_code": 0,
        "elapsed_time": 0,
        "phase": "dns",
        "type": "dns.address_changed"
      }
    }
  4. 用户代理再试图向 192.0.2.1 发送新请求,但依然无法建立连接。尽管用户代理曾经从 192.0.2.1 获得过 NEL 策略,但策略记录的“接收 IP 地址”仅保留最近一次获取时的(这里为 192.0.2.2),因此依然需要降级为 dns.address_changed

    {
      "age": 0,
      "type": "network-error",
      "url": "https://example.com/",
      "body": {
        "sampling_fraction": 1.0,
        "server_ip": "192.0.2.1",
        "protocol": "http/1.1",
        "method": "GET",
        "request_headers": {},
        "response_headers": {},
        "status_code": 0,
        "elapsed_time": 0,
        "phase": "dns",
        "type": "dns.address_changed"
      }
    }

8. 使用场景

8.1 导航失败的报告

用户发起的导航请求(如点击链接、地址栏直接输入,由用户操作触发的脚本导航等),可能因多种连接性问题而失败:如 DNS 失败、TCP 错误、TLS 协议违规等。这些错误可能由网络配置错误、临时路由问题、服务器宕机、恶意软件或针对用户的攻击等原因造成。

在这种情况下,目标主机往往无法意识到导航失败,因为其根本看不到该请求进入自己的基础设施,也无法调查问题原因。为了解决这个问题,主机可以向用户代理注册一个NEL 策略,指定这些失败报告应投递到哪里,以便进行后续分析。

8.2 第一方子资源拉取失败的报告

一个典型的应用需要几十个资源,这些资源通常通过 HTML、CSS 或 JavaScript 进行拉取。应用本身可以通过 onerror 回调等方式观察到部分失败,但无法获知导致失败的详细网络错误原因,例如 DNS 故障、TCP 错误、TLS 协议错误等。

为解决这一问题,应用可以为用于拉取子资源的第一方主机向用户代理注册相关的NEL 策略。这样,只要策略存在且遇到某一已注册origin资源的网络错误时,用户代理就会报告详细的网络错误,帮助开发者排查故障。

8.3 第三方子资源拉取失败的报告

当资源被第三方嵌入时,资源提供者通常无法埋点或观察到失败。例如 example.com 在站点中嵌入了 widget.com/thing.js 资源,而访问 example.com 的用户如果因网络错误拉取该资源失败,widget.com 主机既不知晓也无法检测此失败。

为解决该问题,widget.com 可以为自身主机注册 NEL 策略。这样,只要策略生效,无论资源是从第一方还是第三方 origin 拉取,只要遇到网络错误——只要目标origin注册了NEL 策略,用户代理就会报告网络错误并使服务商能够排查问题。

9. 隐私注意事项

NEL 提供的网络错误报告有可能暴露用户网络配置的新信息。例如攻击者可以滥用 NEL 上报来探测用户的网络结构,或者扫描用户内网服务器。此外,类似 HSTS、HPKP 和 CSP 钉扎策略,已存储的NEL 策略也可被用作“超级 Cookie”,通过定制唯一的(每用户)上报 URI 用于标识符,替代或协同 HTTP cookie 实现跟踪。

为降低上述风险,NEL 注册仅限于潜在可信源点,网络错误报告的投递同样只允许发往潜在可信源点。这样可防止短暂的 HTTP MITM 利用 NEL 作为持久追踪器。

此外,NEL 的 策略缓存网络分区密钥进行分区,使得同一站点在不同嵌套场景下存储的NEL 策略不会被另一上下文复用(比如,被不同顶级站点嵌入时)。

NEL 旨在补充现有服务端监控功能。NEL 报告应只发给当前被请求服务的所有者。对于DNS 解析阶段发生的错误,只有当 NEL 策略由该错误域名命名树的所有者下发时,NEL 才会上报错误。对于发生在安全连接建立请求与响应传输阶段的错误,则只有 NEL 策略来自请求实际发往服务器的所有者时才会上报。

上述原因解释了 NEL 策略的接收 IP 地址子域标志的处理方式。通过检查策略的接收 IP 是否与服务器IP一致,NEL 将策略的信任边界同时扩展到服务器,使其不仅仅信任策略origin,还要求校验实际通信服务器。这样可防止如 DNS 重绑定攻击(一方下发长期有效 NEL,随后变更 name server,把该 origin 指向另一个自己不控制服务器,导致用户代理把第二台服务器的报告发给攻击者)。

同样,子域相关的 NEL 策略也有限制,只允许在策略源点子域的DNS 解析阶段时上报。此时还未连接服务器,因此只需验证策略是由该 origin的超级域送达即可,允许域名所有者以 NEL 检测7.3 DNS 配置错误,防止恶意 DNS 收集不属于所有者的信息。

为防止信息泄露,NEL 关于某个 请求 的报告不会包含服务器在处理该请求时无法看到的任何信息。对DNS 解析阶段的错误,报告内容仅限于 DNS 层可获取信息。这可防止服务器滥用 NEL 收集更多用户信息。注意 NEL 报告会包含网站公网 IP(server_ip 字段),该 IP 未必总被服务端知晓(如位于负载均衡后)。

例如,NEL 报告不会包含任何关于具体使用了哪台 DNS 解析器解析请求domain name的信息。

除上述约束外,用户代理必须

开发部署 NEL 时应当考虑 NEL 报告投递给指定收集器后的隐私影响。例如,报告可能包含带有敏感数据的 URL(如“能力型 URL”),需要特殊防护(见[CAPABILITY-URLS]), 并可能需要开发者自行部署 NEL 收集器以防止这类 URL 被第三方收到。

10. IANA 注意事项

应按如下登记更新永久消息头字段注册表(见[RFC3864]):

10.1 NEL

Header field name
NEL
Applicable protocol
http
Status
standard
Author/Change controller
W3C
Specification document
本规范(见NEL 响应头)

A. 索引

A.1 本规范定义的术语

A.2 引用规范定义的术语

B. 致谢

本文件根据 [CSP] 和 [RFC6797] 规范的许可,复用了部分内容。此外,特别感谢 Julia Tuttle、Chris Bentzel、Todd Reifsteck、Aaron Heady 和 Mark Nottingham 对本工作所做出的宝贵评论与贡献。

C. 参考文献

C.1 规范性引用

[CAPABILITY-URLS]
能力型URL的良好实践。Jeni Tennison。W3C。2014年2月18日。第一公开工作草案。URL: https://www.w3.org/TR/capability-urls/
[CSP]
内容安全策略第3级。Mike West; Antonio Sartori。W3C。2025年4月30日。W3C工作草案。URL: https://www.w3.org/TR/CSP3/
[ECMA-262]
ECMAScript 语言规范。 Ecma International。URL: https://tc39.es/ecma262/multipage/
[fetch]
Fetch 标准。Anne van Kesteren。WHATWG。 现行标准。URL: https://fetch.spec.whatwg.org/
[hr-time]
高分辨率时间。Yoav Weiss。W3C。2024年11月7日。W3C 工作草案。URL: https://www.w3.org/TR/hr-time-3/
[html]
HTML 标准。Anne van Kesteren; Domenic Denicola; Dominic Farolino; Ian Hickson; Philip Jägenstedt; Simon Pieters。WHATWG。现行标准。URL: https://html.spec.whatwg.org/multipage/
[HTTP-JFV]
HTTP 头字段值的 JSON 编码。J. Reschke。IETF。2017年10月24日。现行 Internet-Draft。URL: https://datatracker.ietf.org/doc/html/draft-reschke-http-jfv
[infra]
Infra 标准。Anne van Kesteren; Domenic Denicola。WHATWG。现行标准。URL: https://infra.spec.whatwg.org/
[mixed-content]
混合内容。Emily Stark; Mike West; Carlos IbarraLopez。W3C。2023年2月23日。候选推荐。URL: https://www.w3.org/TR/mixed-content/
[network-reporting]
网络报告API。W3C。编辑草案。URL: https://w3c.github.io/reporting/network-reporting.html
[referrer-policy]
引用策略。Jochen Eisinger; Emily Stark。W3C。2017年1月26日。W3C候选推荐。URL: https://www.w3.org/TR/referrer-policy/
[REPORTING]
报告 API。Douglas Creager; Ian Clelland; Mike West。W3C。2024年8月13日。W3C 工作草案。URL: https://www.w3.org/TR/reporting-1/
[RESOURCE-TIMING-2]
资源计时。Yoav Weiss; Noam Rosenthal。W3C。2025年2月13日。候选推荐。URL: https://www.w3.org/TR/resource-timing/
[RFC1034]
域名——概念与机制。P. Mockapetris。IETF。1987年11月。国际标准。URL: https://www.rfc-editor.org/rfc/rfc1034
[RFC1123]
互联网主机需求——应用与支持。R. Braden, Ed. IETF。1989年10月。国际标准。URL: https://www.rfc-editor.org/rfc/rfc1123
[RFC2119]
在RFC中指示需求等级的关键字。S. Bradner。IETF。1997年3月。最佳当前实践。URL: https://www.rfc-editor.org/rfc/rfc2119
[RFC3864]
消息头字段注册流程。G. Klyne; M. Nottingham; J. Mogul。IETF。2004年9月。最佳当前实践。URL: https://www.rfc-editor.org/rfc/rfc3864
[RFC4291]
IPv6 地址架构。R. Hinden; S. Deering。IETF。2006年2月。草案标准。URL: https://www.rfc-editor.org/rfc/rfc4291
[RFC5234]
语法规范的增强BNF。D. Crocker, Ed.; P. Overell。IETF。2008年1月。国际标准。URL: https://www.rfc-editor.org/rfc/rfc5234
[RFC6797]
HTTP 严格传输安全(HSTS)。J. Hodges; C. Jackson; A. Barth。IETF。2012年11月。提案标准。URL: https://www.rfc-editor.org/rfc/rfc6797
[RFC8174]
RFC 2119 关键字大小写歧义。B. Leiba。IETF。2017年5月。最佳当前实践。URL: https://www.rfc-editor.org/rfc/rfc8174
[RFC9110]
HTTP 语义。R. Fielding, Ed.; M. Nottingham, Ed.; J. Reschke, Ed. IETF。2022年6月。国际标准。URL: https://httpwg.org/specs/rfc9110.html
[RFC9112]
HTTP/1.1。R. Fielding, Ed.; M. Nottingham, Ed.; J. Reschke, Ed. IETF。2022年6月。国际标准。URL: https://httpwg.org/specs/rfc9112.html
[secure-contexts]
安全上下文。Mike West。W3C。 2023年11月10日。候选推荐。URL: https://www.w3.org/TR/secure-contexts/
[url]
URL 标准。Anne van Kesteren。WHATWG。 现行标准。URL: https://url.spec.whatwg.org/