网络错误日志记录

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 (拉取请求, 新建议题, 开放议题)

摘要

本文档定义了一种机制,使开发者能够为 Web 应用声明网络错误报告策略。用户代理可以使用此策略报告 所遇到的、导致其无法成功获取所请求资源的网络错误。

本文档的状态

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

本文档由 Web 性能工作 组使用 推荐标准轨道发布为 工作草案。

作为工作草案发布并不 意味着获得 W3C 及其成员的认可。

本文档是一份草案,可能随时由其他 文档更新、取代或废弃。除作为进行中的工作外, 不应引用本文档。

本文档由一个 根据 W3C 专利 政策运作的工作组制定。 W3C 维护着一份 所有专利披露的公开 列表, 这些披露与该工作组的交付成果有关; 该页面还包含 专利披露说明。任何实际 知晓某项专利,并认为该专利包含 必要权利要求 的个人,都必须依照 W3C 专利政策第 6 节披露相关信息。

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

1. 简介

准确测量 Web 应用的性能特征,是帮助网站 开发者了解如何改进其 Web 应用的一个重要方面。最糟糕的情况是由于网络错误导致 应用或某个特定资源加载失败,而为了处理此类故障, 开发者需要用户代理提供协助,以确定此类故障发生的时间、位置和原因。

目前,应用开发者无法从最终用户处获得实时的 Web 应用可用性数据。 例如,如果用户由于网络错误(如 DNS 查找失败、 连接超时、连接被重置或其他原因)而无法加载页面,网站开发者就无法检测并处理 此问题。请注意,这类网络错误无法仅在服务器端检测到,因为按照 定义,客户端可能根本无法成功与服务器建立连接。

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

网络错误日志记录(NEL)通过定义一种机制来满足这一需求,使 Web 应用能够声明 一个报告策略,用户代理可使用该策略报告给定源的网络错误。Web 应用通过提供一个 NEL HTTP 响应标头字段来选择使用 NEL,该字段描述所需的 NEL 策略。该策略指示用户代理记录有关向该源发出的请求的信息,并 尝试将这些信息交付到先前使用 Reporting API 配置的一组端点。顾名思义,NEL 报告主要用于描述错误。 不过,为了确定不同客户端群体中的错误,我们还必须 知道发生了多少成功请求;这些成功请求也可以通过 NEL 机制进行报告。

例如,如果用户代理由于 TCP 连接中止而无法从 https://www.example.com 获取资源,则用户代理会通过 Reporting API 将以下报告排入队列:

类型
"network-error"
端点组
report_to 字段配置的端点组
数据
{
  "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 网络获取算法。

无论使用哪种获取算法以及哪种底层应用和 传输协议,处理一个网络请求都由 以下阶段组成:

  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 策略 指示用户代理是否收集有关向某个发出的 网络请求的报告,以及在收集时将其发送到何处。 NEL 策略通过 HTTP 响应标头交付给用户代理。

每个NEL 策略都有一个接收 IP 地址,即用户代理从中接收该 NEL 策略服务器IP 地址

每个NEL 策略都有一个

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

每个NEL 策略都有一个请求标头列表和一个响应 标头列表,其中每个列表都是标头 名称的列表。

每个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 HTTP 响应标头,为其控制的源定义一个NEL 策略

4.1 NEL 响应标头

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

NEL = json-field-value

标头的值被解释为 JSON 对象数组,如 json-field-value 中所定义。数组中的每个对象都为该源定义一个 NEL 策略。用户代理必须处理数组中的第一个有效 策略,并忽略数组中的所有其他策略。

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

用户代理必须忽略通过 meta 元素指定的NEL 标头,以缓解通过脚本攻击劫持错误报告的问题。NEL 策略必须通过 NEL 响应标头交付。

meta 元素的限制与 [CSP] 规范一致,该规范出于相同原因将 报告注册限制为只能通过 HTTP 标头字段进行。

4.1.1 report_to 成员

report_to 成员指定此NEL 策略的报告将被发送到的端点 组。 注册NEL 策略时,report_to 成员是必需的; 如果目的是移除先前的注册,则它是可选的——参见 max_age。如果存在,其值必须是字符串;任何其他类型 都会导致解析错误。

为了改善 NEL 报告的交付,服务器应将 report_to 设置为一个端点组,其中至少包含 一个位于替代源中的端点,且该替代源的基础设施不与正在获取资源的源 耦合——否则,在问题得到解决之前(如果最终能够解决),网络错误将无法报告—— 并提供多个端点,以便在某些端点无法访问时提供替代方案。

4.1.2 max_age 成员

必需的 max_age 成员指定此NEL 策略的 生命周期,以非负整数秒数表示。其值必须是非负整数;任何其他类型都会 导致解析错误。

0 将导致此 的任何NEL 策略策略缓存中移除。

为确保 NEL 报告能够交付,服务器应确保 Reporting 端点组也配置了足够大的 max_age。如果 Reporting 策略 过期,则即使 NEL 策略尚未 过期,也不会交付 NEL 报告。

4.1.3 include_subdomains 成员

可选的 include_subdomains 成员是一个 布尔值,它为此 源的所有子域(对子域深度不设限制)启用此NEL 策略。如果对象中不存在名为 include_subdomains 的成员,或者其值 不是 true,则不会为子域启用该NEL 策略

为确保子域的 NEL 报告能够交付,应用应 确保 Reporting 端点组也启用了 include_subdomains。如果 Reporting 策略没有 启用它,并且某个给定子域也没有单独的 Reporting 策略, 则即使 NEL 策略包含该子域,也不会交付该子域的 NEL 报告。

4.1.4 success_fraction 成员

可选的 success_fraction 成员定义 应应用于有关此源的 成功网络请求报告的 采样率。 如果存在, 其值必须是介于 0.01.0 之间(含两端)的数字;任何其他值都会导致解析 错误。如果不存在此成员,则用户代理将不会 收集有关此源的成功网络请求的 NEL 报告。

4.1.5 failure_fraction 成员

可选的 failure_fraction 成员定义 应应用于有关此源的 失败网络请求报告的 采样率。如果存在,其 值必须是介于 0.01.0 之间 (含两端)的数字;任何其他值都会导致解析错误。如果不存在此成员, 用户代理将收集有关此源的 所有失败网络请求的 NEL 报告。

4.1.6 request_headers 成员

可选的 request_headers 成员定义 一个请求标头列表,这些标头的名称将包含在有关此网络错误报告中。如果存在,其值必须 是一个字符串 列表。

4.1.7 response_headers 成员

可选的 response_headers 成员定义 一个响应标头列表,这些标头的 名称将包含在有关此网络错误报告中。如果存在,其值必须 是一个字符串 列表。

4.2 处理策略标头

给定一个网络请求request)及其对应的 响应response),此算法为 requestNEL 策略提取其,并相应地更新 策略缓存

  1. 如果以下任一条件为真,则中止这些步骤:
  2. originrequest
  3. key 为给定 request保留客户端,调用确定网络 分区键所得的结果。
  4. header 为名称为 NEL响应标头的值。
  5. list 为执行 [HTTP-JFV] 第 4 节中定义的算法 对 header 所得的结果。如果该算法 导致错误,或者 list 为空,则中止这些 步骤。
  6. itemlist 的第一个元素。
  7. 如果 item 没有名为 max_age 的成员,或者该 成员的值不是数字,则中止这些步骤。
  8. 如果 itemmax_age 成员的值为 0,则从策略 缓存中移除其origin 的任何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
    子域标志
    如果 item 有一个名为 include_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. originrequest
  2. key 为给定 request保留客户端,调用确定网络 分区键所得的结果。
  3. 如果策略缓存中存在 (key, origin) 的条目:
    1. policy 为该条目。
    2. 如果 policy过期,则返回它。
  4. 对于 origin 的每个超域匹配 parent origin
    1. 如果策略缓存中存在 (key, parent origin) 的条目:
      1. policy 为该条目。
      2. 如果 policy过期,并且其 子域标志为 include, 则返回它。
  5. 返回 no policy

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. 如果对 request执行“源是否可能 可信?”算法的结果 不是 Potentially Trustworthy,则返回 null。
  2. originrequest
  3. policy 为对 request 执行 5.1 为请求选择策略所得的结果。如果 policyno 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,则将以下属性追加到 report body
    server_ip
    用户代理向其发送 请求的服务器的 IP 地址(如果可用)。否则为空字符串。
    • 由 IPv4 地址标识的主机以 点分十进制表示法表示(由四个范围在 0 到 255 之间的十进制数字组成的序列,以“.”分隔)。[RFC1123]
    • 由 IPv6 地址标识的主机表示为 八个 16 位部分的有序列表(即 x:x:x:x:x:x:x:x 序列,其中每个“x”是该地址八个 16 位部分之一的一到四位十六进制 数字)。[RFC4291]
    protocol
    用于获取资源的网络协议, 由 ALPN 协议 ID 标识(如果可用)。否则为 ""
  8. 如果 report bodyphase 属性不是 dnsconnection,则将以下 属性追加到 report body
    referrer
    request 的 referrer,由与其客户端关联的referrer 策略确定。
    method
    request请求方法
    request_headers
    requestpolicy 执行 5.2 提取请求标头所得的结果。
    response_headers
    responsepolicy 执行 5.3 提取响应标头所得的结果。
    status_code
    HTTP 响应的状态码(如果可用)。否则为 0
  9. 如果 origin 不等于 policypolicy子域标志为 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 报告。 这是一种隐私保护措施,可确保 NEL 报告只发送 给报告所描述服务的所有者。如果 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. 使用以下参数生成网络报告

    类型
    network-error
    数据
    report body
    端点组
    policy报告组
    url
    url 运行URL 序列化器所得的结果。

6. 预定义网络错误类型

有若干预定义的网络错误类型

用户代理可以使用自定义网络错误 类型扩展此列表——例如, 为了适应新协议,或者提供现有协议的更详细错误 描述。这样做时,用户代理应该类型名称遵循以点分隔的模式 ([group].[optional-subgroup].[error-name]),以便 简单且一致地处理错误报告——例如,收集器可以按类别 和/或一个或多个子组进行聚合。

6.1 DNS 解析错误

本节中的所有网络错误都发生在DNS 解析期间,因此其阶段dns

dns.unreachable
DNS 服务器无法访问
dns.name_not_resolved
DNS 服务器已响应,但无法解析该地址
dns.failed
由于前述错误未涵盖的原因,对 DNS 服务器的请求失败
dns.address_changed
表示自收到相应的NEL 策略以来,请求的所解析到的 IP 地址 已发生变化

6.2 安全连接 建立错误

本节中的所有网络错误都发生在安全 连接建立期间,因此其 阶段connection

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 请求和 响应传输错误

本节中的所有网络错误都发生在 请求和响应的传输期间, 因此其阶段application

http.error
用户代理成功接收到响应,但其状态码为 4xx5xx
http.protocol.error
由于 HTTP 协议错误,连接被中止
http.response.invalid
响应为空、content-length 不匹配、编码不正确和/或存在其他 阻止用户代理处理响应的情况
http.response.redirect_loop
由于检测到重定向循环,请求被中止
http.failed
由于前述错误未涵盖的 HTTP 协议错误,连接失败
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 策略遇到网络错误时,用户代理可能会将这些报告排入队列。这里展示了 [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. 一段时间后,用户代理再次向 example.com 发送请求。用户代理的本地缓存中仍有 原始资源的副本,并在 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 地址的源

对于其域名解析为多个 IP 地址的,NEL 有时会“降级”错误报告,提供 较少的错误原因信息,因为它无法验证 的所有者是否与处理请求服务器所有者相同。

例如,假设 example.com 由三台 服务器处理,每台服务器具有不同的 IP 地址。服务所有者 配置 DNS,将 example.com 解析为 192.0.2.1192.0.2.2192.0.2.3,并依赖用户代理在 这三个 IP 地址之间均衡其请求。服务所有者交付 以下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。由于接收 IP 地址服务器的 IP 地址匹配(对于任何 成功请求都必须如此),因此它生成以下 NEL 报告:

    {
      "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 发送一个请求,但无法与 服务器建立连接。用户代理的策略缓存中仍有该NEL 策略,理想情况下会使用该策略 为这个失败的 网络请求生成 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 策略。然后,如果存在此类策略,并且从具有已注册的资源遇到网络错误,而该源具有已注册的NEL 策略,则用户 代理将报告详细的网络错误报告,使应用开发者能够调查 该错误。

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

当资源由第三方嵌入时,资源提供者通常无法 检测和观察失败。例如,如果 example.com 在其网站上嵌入 widget.com/thing.js 资源,而访问 example.com 的用户 因网络错误而无法获取该资源,则 widget.com 主机既不知道 该失败,也无法检测到它。

为解决此问题,widget.com 可以为其主机注册 NEL 策略。然后,如果存在此类策略, 并且在获取资源时遇到网络错误——无论该资源是从 第一方还是第三方源请求的——只要该资源来自具有已注册NEL 策略,用户 代理就会报告网络错误,使提供者能够调查该错误。

9. 隐私注意事项

NEL 提供的网络错误报告可能会暴露有关 用户网络配置的新信息。例如,攻击者可能滥用 NEL 报告来探测用户的网络配置,或扫描 用户内部网络中的服务器。此外,与 HSTS、HPKP 和 固定的 CSP 策略类似,存储的NEL 策略可被用作 “超级 Cookie”,方法是设置一个不同的策略,并使用自定义的(每用户) 报告 URI 充当标识符,与 HTTP Cookie 结合使用(或替代 HTTP Cookie)。

为缓解上述部分风险,NEL 注册仅限于 可能可信的源,网络错误 报告的交付同样仅限于可能可信的源。 这可防止临时 HTTP MITM 轻易滥用 NEL 作为 持久跟踪器。

此外,NEL 策略缓存使用 网络分区键进行分区,因此为某个 网站在一种嵌入上下文中存储的NEL 策略不会在不同上下文中使用 (例如,被不同顶级网站嵌入时)。

NEL 旨在增强现有的服务器端监控。NEL 报告 应仅发送给所请求服务的所有者。对于 发生在DNS 解析期间的错误,仅当NEL 策略是从包含策略源域名命名空间树所有者处接收到时,才会生成 NEL 报告。对于 发生在安全连接 建立请求和响应的传输期间的错误,仅当 NEL 策略是从服务器的所有者处接收到时,才会生成 NEL 报告,而请求正是 发送到该服务器的。

此理由解释了NEL 策略接收 IP 地址子域标志的处理方式。通过检查 策略的接收 IP 地址是否与 服务器的 IP 地址匹配,NEL 将策略的信任边界扩展为不仅 包括策略的,还包括 用户代理正在与之通信的特定服务器。这有助于 防止(例如)DNS 重绑定攻击,在这种攻击中,攻击者从其拥有的服务器交付一个 长期有效的NEL 策略,然后更改 其名称服务器,使策略源解析到一个 其无法控制的服务器。如果没有接收 IP 地址验证,这 将导致用户代理把有关第二台服务器的报告发送给 攻击者。

类似地,子域NEL 策略受到 限制,并且只能用于在策略源的子域的DNS 解析阶段期间生成 请求的报告。在此阶段,不存在需要验证所有权的服务器, 而策略是从请求的超域接收到这一事实,足以确定 错误的所有权。这使特定部分 域名命名空间树的所有者能够使用 NEL 检测7.3 DNS 配置错误 ,同时防止他们 使用恶意 DNS 条目收集有关其 无法控制的服务器的信息。

为防止信息泄露,有关请求的 NEL 报告 不会包含 服务器在 处理请求时不可见的任何信息。对于DNS 解析期间的错误, NEL 报告仅包含 DNS 本身可提供的信息。这 可防止服务器滥用 NEL 来收集超出其已有访问权限的更多 用户信息。请注意,NEL 报告将在报告 正文server_ip 字段中包含网站的公共 IP 地址,而生成 NEL 标头的服务 可能并不总是知道该地址,例如当其位于负载 均衡器或其他透明 MitM 代理之后时。

例如,NEL 报告明确不会包含有关使用了哪个 DNS 解析器请求域名解析为 IP 地址的任何信息。

除上述限制外,用户代理还必须

部署 NEL 时,开发者应该考虑交付给指定收集器的 NEL 报告的隐私影响。 例如,报告可能包含带有敏感数据的 URL(例如 “能力 URL”),这些 URL 可能需要采取特殊预防措施(参见 [CAPABILITY-URLS]), 并且可能要求开发者运行自己的 NEL 收集器,以防止将此类 URL 报告给第三 方。

10. IANA 注意事项

永久消息标头字段注册表应使用以下注册项进行更新([RFC3864]):

10.1 NEL

标头字段名称
NEL
适用协议
http
状态
标准
作者/变更控制者
W3C
规范文档
本规范(参见 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]
Referrer 策略。Jochen Eisinger; Emily Stark。W3C。2017 年 1 月 26 日。W3C 候选推荐标准。URL:https://www.w3.org/TR/referrer-policy/
[REPORTING]
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,编辑。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]
IP 版本 6 寻址 体系结构。R. Hinden;S. Deering。IETF。2006 年 2 月。标准草案。URL:https://www.rfc-editor.org/rfc/rfc4291
[RFC5234]
语法规范的扩充 BNF: ABNF。D. Crocker,编辑;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,编辑; M. Nottingham,编辑;J. Reschke,编辑。IETF。2022 年 6 月。互联网标准。URL:https://httpwg.org/specs/rfc9110.html
[RFC9112]
HTTP/1.1。R. Fielding,编辑;M. Nottingham,编辑;J. Reschke,编辑。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/