1. 简介
本文档为通用报告提供了三项基础设施,可供其他规范使用或扩展:
-
用于定义报告类型和报告端点的通用框架,以及用于通过 HTTP 向端点发送报告的文档格式。
-
用于在文档或 worker 中配置报告端点的具体机制,并用于投递与该文档或 worker 生命周期绑定的报告。
-
用于观察文档或 worker 内生成报告的 JavaScript 接口。
其他规范可以扩展或利用这些基础设施,例如定义具体的报告类型,或为非文档类报告定义其他配置或投递机制。
1.1. 保证
本规范旨在提供一个尽力而为的报告投递系统,其执行与网站活动解耦。用户代理能够更好地优先处理和调度报告投递,因为它能全局了解跨源活动,而单个网站无法做到,并且可以根据阻止网站加载的错误条件来投递报告。
但报告的投递并无任何保证,报告机制也不应被用作可靠的通信通道。网络状况可能导致报告无法送达目的地,用户代理也可以因任何原因拒绝或不投递报告。
1.2. 示例
endpoint-1" 的报告端点:
Reporting-Endpoints: endpoint-1="https://example.com/reports"
以及如下响应头,将 CSP 和 HPKP 报告指向该端点:
Content-Security-Policy: ...; report-to endpoint-1 Public-Key-Pins: ...; report-to=endpoint-1
Reporting-Endpoints: csp-endpoint="https://example.com/csp-reports", hpkp-endpoint="https://example.com/hpkp-reports"
以及如下响应头,将 CSP 和 HPKP 报告分别指向对应端点:
Content-Security-Policy: ...; report-to csp-endpoint Public-Key-Pins: ...; report-to=hpkp-endpoint
2. 通用报告框架
本节定义了报告和端点的通用概念,以及如何将报告序列化为 application/reports+json 格式。
2.1. 概念
2.1.1. 端点
端点(endpoint)是指用于接收特定报告的地址,这些报告属于某个特定源(origin)。
每个端点都有一个name,为 ASCII 字符串。
每个端点都有一个failures,为非负整数,表示该端点连续未响应请求的次数。
2.1.2. 报告类型
报告类型(report type)是一个非空字符串,指定报告体中包含的数据集。
当定义一个报告类型(在本规范或其他规范中)时,可以指定其对
ReportingObserver 可见,即该类型的报告可被报告观察者观察。默认情况下,报告类型对报告观察者不可见。
2.1.3. 报告
报告(report)是一组任意数据,用户代理应将其投递到指定端点。
每个报告有一个body,为
null 或可序列化为JSON 文本的对象。报告体中的字段由报告的类型决定。
每个报告有一个url,
通常为生成报告的 Document 或 Worker 的地址。
注: 序列化 URL 时会去除用户名、密码和片段。参见 § 8.1 能力型 URL。
每个报告有一个user
agent,即生成报告的请求的 User-Agent 头的值。
注: user agent 表示生成报告的页面的 User-Agent。这可能与上传报告到收集器时发送的 User-Agent
不同,例如浏览器选择使用“请求桌面站点”等非默认 UA 字符串时。
每个报告有一个destination,为字符串,表示报告将被发送到的端点的name。
每个报告有一个timestamp,记录报告生成的时间(自 unix 纪元以来的毫秒数)。
每个报告有一个attempts计数器,为非负整数,表示用户代理尝试投递该报告的次数。
2.2. 媒体类型
向指定端点 POST 报告时使用的媒体类型为 application/reports+json。
2.3. 将 data 作为 type 排队到 destination
要根据可序列化对象(data)、字符串(type)、另一个字符串(destination)、可选环境设置对象(settings)和可选URL(url)生成报告:
-
令 report 为一个新报告对象,其值初始化如下:
- body
-
data
- user agent
- destination
-
destination
- type
-
type
- timestamp
-
当前时间戳。
- attempts
-
0
-
如果调用方未提供 url,则令 url 为 settings 的创建 URL。
-
返回 report。
注: 报告中的序列化 URL 会去除用户名、密码和片段。参见 § 8.1 能力型 URL。
注: 用户代理可因任何原因拒绝报告。例如,本 API 不保证可投递任意数量的数据。
注: 非用户代理客户端(无 JavaScript 引擎)不应与报告观察者交互,因此应在第6步直接返回。
2.4. 序列化报告
要将 reports 列表序列化为 JSON:
-
令 collection 为一个空列表。
-
对 reports 中的每个 report:
-
令 data 为包含以下键值对的映射:
age-
report 的 timestamp 与当前时间的毫秒差值。
type-
report 的 type
url-
report 的 url
user_agent-
report 的 user agent
body-
report 的 body
注: 客户端时钟不可靠且可能有偏差,因此我们传递
age属性而非绝对时间戳。参见 § 9.2 时钟偏差 -
将 report 的 attempts 加一。
-
将 data 添加到 collection。
-
-
返回对 collection 执行字节序列化的结果,即将 Infra 值序列化为 JSON 字节。
3. 基于文档的报告
本节定义了为文档(或 worker 脚本)中产生的报告配置报告端点的机制。这类报告的生命周期与其生成的文档或 worker 绑定。
3.1. 文档配置
每个实现 WindowOrWorkerGlobalScope
的对象都有一个 endpoints 列表,
该列表是端点的集合,每个端点的 name
必须唯一。
(唯一性由 § 3.3 响应中处理报告端点中的算法保证。)
每个实现 WindowOrWorkerGlobalScope
的对象都有一个 reports 列表,为报告的集合。
要初始化全局对象的端点列表,给定 WindowOrWorkerGlobalScope
(scope)和 response(response),
将 scope 的 endpoints 设为执行 § 3.3
响应中处理报告端点(传入 response)的结果。
3.2. Reporting-Endpoints HTTP 响应头字段
服务器可以通过 Reporting-Endpoints HTTP 响应头字段,为其返回的文档或 worker
脚本资源定义一组报告端点。
该机制定义见 § 3.2,其处理见 § 3.3。
Reporting-Endpoints HTTP 响应头字段的值用于构建资源的报告配置。
Reporting-Endpoints 是一个字典结构字段(Dictionary
Structured Field)[STRUCTURED-FIELDS]。字典中的每个条目定义了一个可投递报告的端点。条目值必须为字符串。
每个端点由字符串项定义,解释为 URI-reference。若其值不是有效的 URI-reference,则该端点成员必须被忽略。
此外,该成员值所代表的 URL 必须是潜在可信的[SECURE-CONTEXTS]。非安全端点将被忽略。
端点不定义任何参数,指定的参数将被静默忽略。
该头部的 ABNF 语法如下 [RFC5234]:
Reporting-Endpoints = sf-dictionary
3.3. 响应中处理报告端点
给定 response(response),该算法提取并返回端点列表。
-
如果 response 的 HTTPS 状态不是 "
modern",且 response 的 origin 不是潜在可信,则终止这些步骤。 -
令 parsed header 为对 response 的 header list 执行 get a structured field value(参数为 "Reporting-Endpoints" 和 "dictionary")的结果。
-
如果 parsed header 为 null,则终止这些步骤。
-
令 endpoints 为一个空列表。
-
对 parsed header 的每个 name → value_and_parameters:
-
返回 endpoints。
3.4. 报告生成
3.4.1. 生成 type 和 data 的报告
当用户代理要为 Document
或 WorkerGlobalScope
对象(context),
给定字符串(type)、字符串(destination)和可序列化对象(data)时,要生成并排队报告,应执行以下步骤:
-
令 settings 为 context 的 相关设置对象。
-
令 report 为运行 生成报告(参数为 data、type、destination 和 settings)的结果。
-
如果 settings 存在,则
-
令 scope 为 settings 的 全局对象。
-
如果 scope 是实现了
WindowOrWorkerGlobalScope的对象, 则执行 § 4.2 用 report 通知报告观察者,参数为 scope 和 report。
-
-
将 report 添加到 context 的 reports 列表。
3.5. 报告投递
随着时间推移,各种特性会在文档和 worker 中排队一系列报告。用户代理会定期获取当前排队的报告列表,并将其投递到关联的端点。本文档不定义用户代理应遵循的调度计划,假定用户代理有足够的上下文信息以在不影响用户体验的前提下及时投递报告。
也就是说,用户代理应尽快在排队后投递报告,因为报告的数据在生成后不久更有价值。
3.5.1. 发送报告
用户代理为 WindowOrWorkerGlobalScope
对象(context)发送 报告列表(reports)时,执行以下步骤:
-
对 reports 中的每个 report:
-
如果 context 的 endpoints 列表中存在
name等于 report 的 destination 的端点(endpoint):-
将 report 添加到 endpoint map 中 endpoint 的报告列表。
-
否则,从 reports 中移除 report。
-
-
-
对 endpoint map 中的每个 (endpoint, report list) 对:
-
对 report list 中的每个 report:
-
令 origin 为 report 的 url 的 origin。
-
将 report 添加到 origin map 中 origin 的报告列表。
-
-
对 origin map 中的每个 (origin, per-origin reports) 对,异步执行以下步骤:
-
令 result 为对 endpoint、origin 和 per-origin reports 执行 § 3.5.2 尝试将报告投递到端点 的结果。
-
如果 result 为 "
Failure":-
将 endpoint 的
failures加一。
-
-
如果 result 为 "
Remove Endpoint":-
从 context 的 endpoints 列表中移除 endpoint。
-
-
从 reports 中移除每个report。
-
注: 用户代理可以只尝试投递部分收集到的报告或端点(例如,一次性发送所有报告会消耗过多带宽等)。报告仅在尝试投递后才会从缓存中移除,跳过的报告会在之后被投递。
3.5.2. 尝试将 reports 投递到 endpoint
给定 端点
(endpoint)、origin
(origin)和报告列表(reports),该算法会构造一个请求并尝试将其投递到 endpoint。若投递成功返回
"Success",若端点通过 410 响应明确移除自身则返回 "Remove Endpoint",否则返回 "Failure"。
-
令 body 为对 reports 执行 将报告列表序列化为 JSON 的结果。
-
令 request 为一个新请求,属性如下 [FETCH]:
method-
"
POST" url-
endpoint 的
url origin-
origin
header list-
包含名为
Content-Type且值为application/reports+json的header list client-
null window-
"
no-window" service-workers mode-
"
none" initiator-
""
destination-
"
report" mode-
"
cors" unsafe-requestflag-
已设置
credentials-
"
same-origin" body
注: 报告发送时
credentials设为same-origin。这允许与报告页面同源的端点获得更多上下文,例如了解某用户是否持续触发错误,或某些操作序列是否导致报告。这不会向端点泄露其本可通过其他方式获得的新信息。跨域端点不会获得凭据。 -
等待响应(response)。
-
如果 response 的
status是OK 状态 (200-299),返回 "Success"。 -
如果 response 的
status为410 Gone[RFC9110],返回 "Remove Endpoint"。 -
返回 "
Failure"。
3.6. 报告中使用的 URL 处理
要报告中使用的 URL 处理,给定 URL url,执行以下步骤,返回报告中使用的 URL 字符串。4. 报告观察者
报告观察者用于从
JavaScript 观察某些类型的报告,在 JavaScript 中由 ReportingObserver
对象表示。
每个实现 WindowOrWorkerGlobalScope
的对象都有一个 已注册报告观察者列表,
是有序集合,元素为报告观察者。
任何在已注册报告观察者列表中的报告观察者都被视为已注册。
每个实现 WindowOrWorkerGlobalScope
的对象都有一个 报告缓冲区,为在该 WindowOrWorkerGlobalScope
中生成的报告的列表。初始为空,报告按生成顺序存储。
注: 报告缓冲区的目的是允许报告观察者观察早于其创建时机生成的报告(通过 buffered
选项)。例如,某些报告可能在页面加载早期阶段生成,此时观察者尚未创建,或 JavaScript 库尚未加载。
注: 报告观察者仅适用于带有 JavaScript 引擎的用户代理。
4.1.
接口 ReportingObserver
dictionary ReportBody { };dictionary Report {DOMString ;type DOMString ;url ReportBody ?; }; [body Exposed =(Window ,Worker )]interface {ReportingObserver constructor (ReportingObserverCallback ,callback optional ReportingObserverOptions = {});options undefined observe ();undefined disconnect ();ReportList takeRecords (); };callback =ReportingObserverCallback undefined (sequence <Report >,reports ReportingObserver );observer dictionary {ReportingObserverOptions sequence <DOMString >;types boolean =buffered false ; };typedef sequence <Report >;ReportList
Report 是面向应用暴露的报告表示。
ReportBody 是抽象字典类型,具体报告类型应继承自它。
每个 ReportingObserver
对象有如下关联概念:
-
创建时设置的回调函数。
-
名为 options 的
ReportingObserverOptions字典。 -
名为 报告队列 的
Report对象列表,初始为空。
ReportList
表示 Report
的序列,开发者可像操作 JavaScript 数组一样使用。
ReportingObserver(callback, options)
构造函数被调用时,执行以下步骤:
-
创建一个新的
ReportingObserver对象 observer。 -
将 observer 的 回调设为 callback。
-
将 observer 的 options 设为 options。
-
返回 observer。
observe() 方法被调用时,执行以下步骤:
-
令 global 为 相关全局对象。
-
将 this 添加到 global 的 已注册报告观察者列表。
-
对 global 的 报告缓冲区中的每个 report,排队任务执行 § 4.3 将报告添加到观察者,参数为 report 和 this。
disconnect() 方法被调用时,执行以下步骤:
-
令 global 为 this 的相关全局对象。
-
从 global 的 已注册报告观察者列表中移除 this。
takeRecords() 方法被调用时,执行以下步骤:
4.2. 在 scope 上用 report 通知报告观察者
该算法使 report 的内容可被提供的 WindowOrWorkerGlobalScope
上所有已注册的报告观察者获取。
-
对 scope 上每个已注册的
ReportingObserverobserver,执行 § 4.3 将报告添加到观察者,参数为 report 和 observer。 -
将 report 添加到 scope 的 报告缓冲区。
-
令 type 为 report 的 type。
4.3. 将 report 添加到 observer
给定 报告 report 和 ReportingObserver
observer,该算法会将 report 添加到 observer 的报告队列,前提是
report 的 type 可被 observer 观察。
-
如果 report 的 type 不对 ReportingObserver 可见,则返回。
-
创建一个新的
Reportr,其type初始化为 report 的 type,url初始化为 report 的 url,body初始化为 report 的 body。
-
将 r 添加到 observer 的 报告队列。
-
如果 observer 的 报告队列 长度为 1:
-
令 global 为 observer 的 相关全局对象。
-
排队任务,以 global 的 已注册报告观察者列表副本为参数,执行 § 4.4 用 notify list 调用报告观察者。
-
4.4. 用 notify list 调用报告观察者
该算法为先前观察到的报告调用观察者回调函数。
-
对 notify list 中每个
ReportingObserverobserver:
5. 实现注意事项
5.1. 投递
用户代理应尽快尝试投递报告,以便开发者能尽早获得反馈。但在用户体验和及时反馈之间权衡时,应优先考虑用户体验。因此,用户代理可以根据对用户活动和上下文的了解,延迟报告的投递。
例如,用户代理应将报告数据的传输优先级低于其他网络流量。用户在网站上的显式操作应优先于报告流量。
用户代理可以选择在用户处于快速、低价网络时才投递报告,以避免不必要的数据费用。
用户代理可以选择优先投递来自特定源(如用户经常访问的站点)的报告。
5.2. 垃圾回收
6. 报告示例
本节为非规范性内容。
本示例展示了用户代理向报告端点发送报告时的格式。示例提交包含三个报告,这些报告被打包在一个 HTTP 请求中发送。(报告类型和内容仅为示例,与实际功能无关,超出本规范范围。)
POST / HTTP/1.1
Host: example.com
...
Content-Type: application/reports+json
[{
"type": "security-violation",
"age": 10,
"url": "https://example.com/vulnerable-page/",
"user_agent": "Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0",
"body": {
"blocked": "https://evil.com/evil.js",
"policy": "bad-behavior 'none'",
"status": 200,
"referrer": "https://evil.com/"
}
}, {
"type": "certificate-issue",
"age": 32,
"url": "https://www.example.com/",
"user_agent": "Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0",
"body": {
"date-time": "2014-04-06T13:00:50Z",
"hostname": "www.example.com",
"port": 443,
"effective-expiration-date": "2014-05-01T12:40:50Z",
"served-certificate-chain": [
"-----BEGIN CERTIFICATE-----\n
MIIEBDCCAuygAwIBAgIDAjppMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT\n
...
HFa9llF7b1cq26KqltyMdMKVvvBulRP/F/A8rLIQjcxz++iPAsbw+zOzlTvjwsto\n
WHPbqCRiOwY1nQ2pM714A5AuTHhdUDqB1O6gyHA43LL5Z/qHQF1hwFGPa4NrzQU6\n
yuGnBXj8ytqU0CwIPX4WecigUCAkVDNx\n
-----END CERTIFICATE-----",
...
]
}
}, {
"type": "cpu-on-fire",
"age": 29,
"url": "https://example.com/thing.js",
"user_agent": "Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0",
"body": {
"temperature": 614.0
}
}]
7. 自动化
为支持用户代理自动化和应用测试,本文档为 [WebDriver] 规范定义了一些 扩展命令。
7.1. 生成测试报告
生成测试报告 扩展命令用于测试目的,模拟生成一个报告。该报告会被所有已注册的报告观察者观察到。
该扩展命令定义如下:
dictionary {GenerateTestReportParameters required DOMString ;message DOMString = "default"; };group
| HTTP 方法 | URI 模板 |
|---|---|
POST
| /session/{session id}/reporting/generate_test_report
|
其 远端步骤如下:
-
如果 parameters 不是 JSON 对象,返回 WebDriver 错误,错误码为 invalid argument。
-
如果 message 不存在,返回 WebDriver 错误,错误码为 invalid argument。
-
如果当前浏览上下文已关闭,返回 WebDriver 错误,错误码为 no such window。
-
处理所有用户提示,如返回 WebDriver 错误则直接返回。
-
令 group 为 parameters 的
group属性。 -
令 body 为一个可序列化为 JSON 文本的新对象,包含一个字符串字段 body_message。
-
将 body_message 设为 message。
-
执行 生成并排队报告,参数为 body、"test"、group 和 settings。
-
返回 success,数据为 null。
8. 安全性注意事项
8.1. 能力型 URL
某些 URL 本身就具有价值。它们可能在用户名和密码部分包含显式凭据,或仅凭 URL 路径即可授予资源访问权限。此外,URL 的 fragment 可能包含本不应离开用户浏览器的信息。更多信息参见 [CAPABILITY-URLS]。
为降低此类 URL 通过报告机制泄露的风险,相关算法会在作为报告来源发送的 URL 中去除凭据和 fragment 信息。但 URL 路径中的敏感信息仍有可能被泄露。使用此类 URL 的站点可能需要自建报告端点。
此外,此类 URL 也可能出现在报告的body中。扩展本 API 并在报告body中包含 URL 的规范,应要求同样去除敏感信息。
9. 隐私注意事项
9.1. 网络泄露
由于页面加载与报告生成、发送之间存在延迟,完全可能出现用户在一个网络上生成报告、但在另一个网络上发送报告的情况。
不过,这种行为仅限于生成报告的文档生命周期内。即使文档关闭,也可能通过 navigator.sendBeacon 等机制在新网络上产生流量。
需考虑缓解措施。例如,网络切换时可丢弃报告。[WICG/background-sync Issue #107]
9.2. 时钟偏差指纹
每个报告随 age 属性一同投递,而不是生成时的时间戳。这样做是因为每个用户本地时钟与服务器时钟可能有任意偏差。报告生成与发送的时间差是稳定的,可以避免通过本 API
暴露时钟偏差带来的指纹风险。
9.3. 跨源关联
如果多个源都使用同一个报告端点,该端点可能获知某用户访问过哪些网站,因为它会收到带有源信息的报告。这种能力并不比当前合作源间可追踪的信息更强,也不会带来超出 <img>
等机制的新追踪能力。
9.4. 禁用报告
报告机制在整体上对所有人都有益处。开发者能及时修复 bug,用户也能间接受益。例如,内容安全策略(CSP)通过报告潜在漏洞,为跨站脚本攻击提供“群体免疫”。修复这些漏洞有利于所有用户,即使他们的用户代理不支持 CSP。
当然,具体价值取决于所传递数据的性质和报告端点的可信程度,但总体上这是有益的。
但这种整体利益不能凌驾于用户个体选择退出的权利之上。发送报告会消耗带宽,也可能泄露网站本身无法直接获得的少量额外信息(如 [NETWORK-ERROR-LOGGING])。用户代理必须允许用户以合理粒度禁用报告,以维护 [HTML-DESIGN-PRINCIPLES] 所倡导的优先级。
10. IANA 注意事项
10.1.
Reporting-Endpoints 头部
应更新永久消息头字段注册表,内容如下:[RFC3864]
- 头字段名称
-
Reporting-Endpoints - 适用协议
-
http
- 状态
-
standard
- 作者/变更控制者
-
W3C
- 规范文档
10.2.
application/reports+json 媒体类型
- 类型名称
-
application
- 子类型名称
-
reports+json
- 必需参数
-
N/A
- 可选参数
-
N/A
- 编码注意事项
-
编码注意事项与 "application/json" 媒体类型一致。参见 [RFC8259]。
- 安全性注意事项
-
见 § 8 安全性注意事项。
- 互操作性注意事项
-
本文档规定了符合要求的消息格式及其解释。
- 已发布规范
- 使用该媒体类型的应用
- 片段标识符注意事项
- 附加信息
- 片段标识符注意事项
-
N/A
- 联系人及邮箱
-
本文件编辑者。
- 预期用途
-
COMMON
- 使用限制
-
N/A
- 作者
-
本文件编辑者。
- 变更控制者
-
W3C
- 临时注册?
-
是