另请参阅 翻译。
Copyright © 2017-2023 World Wide Web Consortium. W3C® liability, trademark and permissive document license rules apply.
W3C Web of Things(WoT)旨在实现跨 IoT 平台和应用领域的互操作性。 实现这一目标的一个关键机制,是定义和使用元数据, 以适当的抽象层级描述 IoT 设备或服务在网络上提供的交互。 WoT Thing Description 规范满足了这一目标。
但是,为了使用一个 Thing,首先必须获得它的 Thing Description。 本文档中描述的 WoT Discovery 过程正是为了解决这一问题。 WoT Discovery 需要支持在各种使用场景中分发 WoT Thing Description。这包括临时系统和工程化系统;开发期间和运行时; 以及本地网络和全球网络。该过程还需要与现有发现机制协同工作, 保证安全,保护私有信息,并且能够高效处理 WoT Thing Description 的更新,以及 IoT 生态系统动态且多样的特性。
WoT Discovery 过程分为两个阶段:Introduction 和 Exploration。 Introduction 阶段利用现有发现机制,但不直接暴露元数据; 它们只是用于发现 Exploration 服务,而这些服务只有在安全认证和授权之后 才会提供元数据。本文档规范性地定义了两种 Exploration 服务: 一种用于从常规 Web 服务分发单个 WoT Thing Description, 其中自描述是一种特殊情况;另一种是可搜索的 WoT Thing Description Directory 服务,用于 Thing Description 集合。 本文档还描述了多种 Introduction 服务,并在必要时给出规范性定义 以支持这些服务。
本节描述本文档在发布时的状态。当前 W3C 出版物列表以及本技术报告的 最新修订版本,可在 W3C 技术报告 索引 https://www.w3.org/TR/ 中找到。
本规范未来的更新可能会纳入 新特性。
本文档由 Web of Things 工作组作为推荐标准, 使用 推荐标准 流程发布。
W3C 建议将本规范作为 Web 标准广泛部署。
W3C 推荐标准是一种规范,它在经过广泛的共识构建之后, 得到 W3C 及其会员的认可,并且 获得了工作组成员对实现所作出的 免版税许可承诺。
本文档由一个依据 W3C 专利政策运作的工作组制作。 W3C 维护着一份 公开的专利披露列表, 其中包含与该工作组交付成果相关的任何专利披露; 该页面还包括披露专利的说明。任何实际知晓某项专利的个人, 若其认为该专利包含 必要 权利要求,则必须按照 W3C 专利政策第 6 节披露相关信息。
本文档受 2023 年 11 月 03 日 W3C 流程 文档约束。
Web of Things(WoT)定义了一种架构, 支持将 Web 技术与 IoT 设备集成并加以使用。 WoT Architecture [wot-architecture11] 文档定义了受支持的基本概念和使用模式。 但是,WoT Thing Description [wot-thing-description11] 是 WoT Discovery 的一项关键规范,因为 WoT Discovery 的 目的就是使 WoT Thing Description 可用。具体而言, WoT Discovery 必须允许经过认证和授权的实体(且仅限这些实体) 查找满足一组条件的 WoT Thing Description,例如具有某些语义, 或包含某些交互。反过来,为了支持安全和隐私目标, WoT Discovery 过程不得向未授权实体泄露信息。这不仅包括 Thing Description 本身所分发的信息,也包括某个给定实体正在请求 某些信息这一事实。
已经有许多发现机制被定义 [Discovery-Categorization-IoT], 因此我们必须说明为什么还要提出一种新的机制。首先, 许多现有发现机制的安全和隐私保护相对较弱。我们的目标之一, 是建立一种不仅使用最佳实践来保护元数据,而且还能够在需要时 升级以支持未来最佳实践的机制。其次,我们以广义方式使用发现, 以同时包含本地和非本地机制。虽然本地机制可能使用广播协议, 但非本地机制可能需要超出当前网络段,因为广播无法扩展, 因此需要另一种方法,例如搜索服务。我们的方法是在需要时使用 现有机制,以自举到一个更通用且安全的元数据分发系统。第三, 我们所分发的元数据,即 WoT Thing Description,是高度结构化的, 并包含数据模式和语义注解等丰富数据。基于简单键值对列表的 现有发现机制并不适合。同时,虽然使用现有语义数据查询标准, 例如 SPARQL [SPARQL11-OVERVIEW], 对某些高级使用场景而言可能是合适的,但对于许多预期的 IoT 应用来说可能需要过多工作。因此,为了处理更基础的应用, 我们还定义了一些更简单的查询机制。
在定义一些基本术语之后,我们将总结 WoT Discovery 的 基本使用场景和要求。这些是 WoT Use Cases [wot-usecases] 和 WoT Architecture [wot-architecture11] 文档中所提出的更详细、更完整的使用场景和要求的一个子集。 然后,我们将描述 WoT Discovery 过程的基本架构, 该架构采用两阶段的 Introduction/Exploration 方法。 此架构的基本目标,是能够使用现有发现标准来为访问受保护的 发现服务进行自举,但仅向授权用户分发详细元数据, 并且还尽可能保护发起查询者免受窃听者影响。随后,我们描述 具体 Introduction 和 Exploration 机制的细节。特别是, 我们详细定义了一个用于 WoT Thing Description Directory(WoT TDD) 服务的规范性 API,该服务为 WoT Thing Description 集合提供搜索机制, 这些集合可以由 Things 或代表它们行事的实体动态注册。 不过,WoT Discovery 机制也支持从常规 Web 服务分发单个 TD, 其中一种特殊情况就是自描述。最后,我们讨论若干安全和隐私考虑, 包括一组潜在风险和缓解措施。
除标记为非规范性的章节之外,本规范中的所有编写指南、 图示、示例和注释均为非规范性内容。本规范中的其他所有内容均为 规范性内容。
本文档中的关键词 MAY、MUST、OPTIONAL、 RECOMMENDED、SHOULD 和 SHOULD NOT 应按照 BCP 14 [RFC2119] [RFC8174] 中的描述进行解释,但仅当它们以全大写形式出现时才如此, 如此处所示。
本节为非规范性内容。
基本的 WoT 术语,例如 Thing、Thing Description(TD)、Thing Model(TM)、Property、Action、 Event、Anonymous TD、Discoverer、Discovery、Exploration、Introduction、Thing Description Server (TD Server)、Thing Description Directory(TDD)、Partial TD、Enriched TD,均在 WoT Architecture 1.1 规范的 第 3 节中定义 [wot-architecture11]。
本节为非规范性内容。
图 1 展示了 WoT Discovery 过程的概览。Discovery 使用两阶段架构, 以调和既要开放、又要将元数据访问限制给授权实体这两种相互竞争的要求。 在第一阶段,可以使用一组相对开放的“Introduction”机制中的一个或多个, 生成一组候选 URL。这些 URL 本身不包含元数据, 但会在第二阶段用于引用“Exploration”服务,这些服务能够在认证之后, 以 Thing Description 的形式实际提供元数据。
其意图是让 Introduction 机制成为相对开放的“首次接触”机制, 为 Discovery 过程的其余部分提供起点。在本文档中,我们为若干 Introduction 机制规定细节,这些机制适用于不同使用场景, 包括本地和非本地场景,但实际上,只要某种机制能够返回 URL, 它就可以提供 Introduction。不过,Introduction 不包含任何安全或 隐私控制,因此不应直接提供元数据。相反,由 Introduction 机制 提供的 URL 会引用“Exploration”服务。Exploration 服务实际会 提供元数据,但只有在应用了适当的认证和访问控制之后才会提供。
Discovery 过程可以从其 Introduction 阶段产生一组 URL 作为输出, 即使只使用了一个 Introduction 机制(某些 Introduction 机制本身 可以返回多个 URL)。Exploration 阶段之后的最终输出也可以是一组 Thing Description。
Introduction 阶段提供的每个 URL 始终指向一个 Exploration 服务端点, 该端点将返回单个 Thing Description。在最简单的情况下, 此 URL 引用由 Web 服务器提供的普通资源,该资源提供描述 IoT 端点设备的 Thing 的 Thing Description。作为一种特殊情况, 对于自描述的 Thing,Introduction URL 可能直接指向由该 Thing 提供的、服务于其自身 Thing Description 的端点。
通常,Thing Description 可以通过各种方式提供,尤其是它们可能不是 自描述的。例如,
此类 Thing 的 Thing Description 应由独立服务提供。
本文档规定了两种允许更高灵活性的特殊情况:
Discovery 过程并不必须检索 Thing Description Directory 的内容, 并将其作为结果的一部分返回,因为通常这可能导致产生庞大的结果集。 相反,应用应扫描结果以查找 Thing Description Directory TD, 并决定是否从中检索 TD,可能还会选择性地检索。同样, 并不要求自动跟随 Thing Link;相反,应用可以选择性地跟随它们。
在本节中,我们将从客户端的角度描述 WoT Discovery 过程, 以及说一个客户端支持 WoT Discovery 意味着什么。我们将使用术语 Discoverer 来表示 作为 WoT Discovery 过程客户端的实体。Discoverer 可以是完整的 Consumer,也可以不是。不过,Discoverer 确实需要读取并提取 Directory 和 Thing Link 的特殊 TD 中的信息,并使用其中提供的 特定 affordance 和链接。反过来,Consumer 可以不支持 Discovery, 虽然这是推荐的 [wot-architecture11]。
WoT Discovery 过程的设计使得几乎任何能够根据单个 URI 获取单个 TD 的客户端,都可以被称为支持 WoT Discovery。当然,Discoverer 可以 支持更强大的 Discovery 机制,但其中一些机制有额外要求。 某些 Introduction 机制可以返回多个 URL,而每个 URL 又可以用于 获取至少一个 TD。因此,即使没有 TDD,也可以发现多个 TD。
以下断言描述了 Discoverer 的具体职责:
@type 字段并作出这种区分。Discoverer 可以决定是否
跟随链接或获取 TDD 内容。在某些使用场景中,Consumer 可能不会扩展
某些 URL,例如指向外部资源的链接,或者当 TDD 包含许多 TD,
获取全部内容将超出 Consumer 的内存处理能力时。
上述过程支持一种让 Directory 引用其他 Directory 而无需复制其 TD 的方式:希望引用其他 Directory 的 Directory 应包含一个 Thing Link, 其 "describedby" 关系指向另一个 Directory 服务的 TD。然后, 上述过程将扩展 Thing Link,以获得该 Directory 的实际 TD, 然后(可选地)使用适当的 Directory affordance 来访问被链接 Directory 的内容。注意,这样的 Thing Link 指向 Directory 的 TD, 而不是指向 Directory 本身。它们可以托管在同一位置,也可以不在同一位置。
递归获取此类被链接 Directory 的内容,尤其是在没有特定查询或过滤器的情况下, 很容易导致下载大量数据。此类递归扩展应限于需要它的使用场景, 例如库存管理、审计或索引。
Directory 服务的 URL 还可以与下文提到的 SPARQL 查询的联邦能力一起使用, 在大多数情况下,这将是一种从一组分布式 Directory 服务中收集特定信息的 更高效方式。不过,SPARQL 需要此类联邦所用 SPARQL 端点的 URL, 该 URL 可以在支持 SPARQL 查询的 Directory 的 TD 中找到。 这与指向 Directory 的 TD 的 URL 并不相同。
本章描述用于与 Things 或 Thing Description Directory 进行初始接触的机制。 Thing 或 Thing Description Directory 可以向 Consumer 提供以下任一机制。 introduction 机制的结果始终是一个 exploration 服务的 URL(地址), 该服务可用于在适当认证之后获得详细元数据(TD)。也可以使用多个 introduction 机制并合并结果。只要能以某种方式获得至少一个 exploration 服务的 URL,就不强制要求使用特定的 introduction 机制。
为了获得 exploration 服务的 URL,可以使用任何会产生单个 URL 的机制 MAY。 这包括 Bluetooth beacon、二维码,以及由用户输入的书面 URL。 对所有此类 URL 的请求 MUST 产生一个 TD, 如 7. Exploration 机制 中所规定。 对于自描述的 Thing,这可以是 Thing 自身的 TD。 如果该 URL 引用一个 Thing Description Directory,则这 MUST 是该 Thing Description Directory 的 Thing Description。
Thing 或 Thing
Description Directory MAY 使用
Well-Known Uniform Resource Identifier [RFC8615]
来通告其存在。 如果
Thing 或 Thing
Description Directory 使用 Well-Known Uniform Resource
Identifier [RFC8615]
来通告其存在,则它 MUST
将自己的 Thing Description 注册到以下路径:
/.well-known/wot。
当对上述 Well-Known URI 发起请求时,服务器 MUST 返回一个 Thing Description,如 7. Exploration 机制 中所规定。
Thing 或 Thing Description Directory MAY 使用 DNS-Based Service Discovery(DNS-SD)[RFC6763]。 这也可以在同一本地网络中与 Multicast DNS(mDNS)[RFC6762] 结合使用。
下表列出了用于通告其存在的服务名称和协议。 这些是规范性的,因为每一项都可用于现有 exploration 机制:
| 服务名称 | Thing 或 TDD | 协议 |
|---|---|---|
_wot._tcp |
Thing | HTTP over TCP、HTTP over TLS/TCP、CoAP over TCP,或 CoAP over TLS/TCP |
_directory._sub._wot._tcp |
TDD | HTTP over TCP、HTTP over TLS/TCP、CoAP over TCP,或 CoAP over TLS/TCP |
_wot._udp |
Thing | CoAP over UDP 或 CoAP over DTLS/UDP |
以下附加服务名称已被定义供未来使用。不过,该定义是资料性的, 因为目前尚未定义使用 CoAP over UDP 的 directory 服务:
| 服务名称 | Thing 或 TDD | 协议 |
|---|---|---|
_directory._sub._wot._udp |
TDD | CoAP over UDP 或 CoAP over DTLS/UDP |
对于基于 TCP 的服务,以下信息
MUST 包含在由 Service
Instance Name 指向的
TXT 记录中:
tdtypeThing 或 Directory。如果省略,
则假定类型为 Thing。
schemehttp(HTTP over TCP)、https
(HTTP over TLS/TCP)、coap+tcp(CoAP over
TCP),或 coaps+tcp(CoAP over TLS/TCP)。
如果省略,则假定 scheme 为
http。
对于基于 UDP 的服务,以下信息
MUST 包含在由 Service
Instance Name 指向的
TXT 记录中:
tdtypeThing 或 Directory。如果省略,
则假定类型为 Thing。
schemecoap(CoAP over UDP)或 coaps
(CoAP over DTLS/UDP)。如果省略,则假定 scheme
为 coap。
图 2 和 图 3 展示了使用 DNS-SD 和 mDNS 支持发现 Thing 或 Thing Description Directory 的示例序列。
Thing 或 Thing Description Directory MAY 使用 Constrained RESTful Environment(CoRE)Link Format [RFC6690] 通告其存在。 Thing 或 Thing Description Directory MAY 使用 CoRE Resource Directory [RFC9176] 来注册指向其对应 Thing Description 的链接。
以 Thing 的 Thing
Description 为目标的 Link 的资源类型
(rt)MUST 为
wot.thing。 以
Thing
Description Directory 的 Thing Description 为目标的 Link
的资源类型 MUST 为
wot.directory。
使用 Decentralized Identifier
(DID)[DID-CORE]
的 Thing 或
Thing
Description Directory
MAY 通过在该 TD 标识符解析到的 DID Document
中分别包含类型为 WotThing 或
WotDirectory 的 DID Service
Endpoint,来通告其 TD 的位置。
为了定义用于
WoT Discovery 的 Service Endpoint,通过解析
Thing 或
Thing
Description Directory 的 DID 所获得的 DID Document
MUST
在其 @context 中包含 URL
https://www.w3.org/2022/wot/discovery-did
[did-spec-registries]。
如果通过解析
Thing 或
Thing
Description Directory 的 DID 所获得的 DID Document 包含类型为
WotThing 或 WotDirectory 的 Service Endpoint,
则该 Service Endpoint MUST 分别引用描述
该 Thing 的 TD(当使用
WotThing 服务名称时),或描述该 Thing
Description Directory 的 TD
(当使用 WotDirectory 服务名称时)
[did-spec-registries]。
图 4 描绘了 TD Server(提供单个 TD,包括用于自描述的 TD)和 Thing Description Directory 服务的高层信息模型。Thing Description Directory 可以包含 TD,同时它本身也是一个 Thing,这意味着它拥有自己的 TD。Directory 还托管用于检索其他 Thing 的单独 TD 的 Web 服务端点,并且其中每一个都可以用作 TD Server。 Thing 通常可以托管自己的 TD,在这种情况下它就是 Self-Describing Thing。Directory 不强制要求自描述,但也可能存在 同时是 Thing Description Directory 和 Self-Describing Thing 的 Self-Describing Thing Description Directory。
两个基本的 exploration 机制在 7.2 Thing Description Server 和 7.3 Thing Description Directory 中描述。
图 5 将 Discovery 本体展示为 Thing 本体的扩展。
该本体包含一个类,用于表示与 Directory 中存储的 TD
相关联的元数据。该类称为 RegistrationInformation,
并作为 Directory 规范的一部分在 7.3.1.1 注册
信息中描述。
Discovery 本体还定义了两个新的 Thing Description 类,它们在以下各节中描述,可用于建模特殊的 exploratory 元数据: ThingDirectory 和 ThingLink。
ThingDirectory,或使用 URI
https://www.w3.org/2022/wot/discovery#ThingDirectory。
此类的 TD 可以从 Directory 的 Thing Model 派生;参见 7.3.2.4 API 规范(Thing Model)。
ThingLink,
或使用 URI
https://www.w3.org/2022/wot/discovery#ThingLink。
Thing Link
MUST 将被引用的 TD 定义为
一个 Link,其 link relation type 为 describedby,
media type 为 application/td+json,并且
href 设置为目标 URL。
示例 3 是一个 Thing Link 示例。
Thing Link 可用于多种场景。例如:
exploration 服务的目的是提供 TD, 但只能在适当认证之后,并且只向授权方提供。不过,在某些情况下, Discoverer 可能不知道通过 exploration 服务访问 TD 需要哪些安全凭据, 尤其是在临时场景中。由于在首次访问 exploration 服务时,如果未提供适当的认证凭据,Discoverer 还不能访问 TD, 因此 Discoverer 不能依赖 TD 中保存的安全元数据来了解需要哪种 认证和授权信息。如果 Discoverer 没有先验知识, 它就必须依赖现有的安全协商支持来为访问进行自举, 至少要先访问 TD 本身。
我们为 HTTP 协议定义以下内容, 对于该协议,安全协商过程已经存在。不过,大多数 HTTP 协商过程都假定 有人类用户参与,但这也适用于 WoT Discovery,因为此问题通常会在用户 尝试访问公共 WoT 服务或执行新设备集成时出现。在这种情况下, 协商的目的是就访问系统需要哪些凭据提供指引。
在使用 exploration 服务来自动化系统管理的情况下, 最好预先确立访问相关 exploration 服务所需的凭据 (以及认证机制),这样就不需要安全自举。因此, 安全自举不是强制性特性,并且可以在将与预先确立的安全机制一起使用的 设备上省略或禁用。
安全自举也可能只在首次访问 TD 时才是必要的。 一旦 Discoverer 已经确定访问某个特定 exploration 服务需要哪些凭据和 认证机制,它们就可以保留此信息,并尝试在未来访问中使用。 但请注意,根据所使用的安全方案,凭据本身可能会过期, 并且可能需要定期重新确立。
任何提供 TD 的 HTTP
端点都 MAY 提供安全自举。
如上所述,如果安全机制已经预先确立,则允许禁用或省略安全自举。
例如,如果某个安装环境希望使用 OAuth2
client flow,并提前向潜在客户端提供要使用的认证服务器地址,
那么可以禁用安全自举,因为替代方案将是包含其他(并且可能更弱的)
认证形式。
在 HTTP 协议中,通常可以通过 HTTP 服务器返回
“401 (Unauthorized)”响应码,并结合指定所需信息的
WWW-Authenticate header,来协商要使用的认证和
授权机制。为了获得访问权限,客户端随后需要使用必要信息再次发起请求。
IANA 注册了若干认证方案。不过,并非所有这些方案都被广泛使用,
有些是实验性的,并且它们与 TD 支持的方案只有部分重叠。
此外,请注意,IANA 注册中的 oauth 方案指的是
OAuth1,它已被弃用,因此不应使用。相关的 OAuth2 flow,
即 code flow,不是以 401 响应开始,
而是以重定向到认证服务器开始,最终产生可用于访问的凭据
(在 WoT 中为 bearer token)。
基于这些考虑,为了在各种设备以及浏览器上启用安全自举, 应遵守以下约束:
WWW-Authenticate header,以及描述所需授权的任何其他
header。关于要求的细节,应查阅上述每种认证方案的 IANA 注册表。
同样,按照 OAuth2 规范,如果在安全自举期间使用 OAuth2
code flow,则必须使用 “302 (Found)” 或 “303 (See Other)”
响应码重定向到认证服务器,访问凭据最终以 bearer token 表示。
请注意,WoT Thing Description 1.1 中支持的另一个 OAuth2 flow
client 期望初始访问的是认证服务器,
而不是最终端点,因此不能通过安全自举使用。这些要求也只适用于
可能需要支持安全自举的 exploration 服务端点,即提供 TD 的端点,
而不适用于同一 exploration 服务可能提供的其他端点。
特别是,这些要求只适用于可以由 introduction 机制引用的 URL,
而不适用于(例如)事件订阅端点。
在 [wot-architecture11] 和 [wot-thing-description11] 中,有与访问 TD 何时需要认证以及使用安全传输相关的安全和隐私考虑。 另见 9. 隐私考虑。总之, 公共服务要求使用安全传输(例如 TLS),即使在私有网络上也强烈建议使用 安全传输(即使没有认证要求,也可保护查询的机密性), 并且只有在不存在 Personally Identifiable Information, 或无法推断出此类信息的有限情况下,才应考虑在没有认证和授权的情况下 提供请求。
任何可以由 URL 引用,并在适当认证和访问控制下返回 TD 的 Web 服务, 都可以用作 exploration 机制。我们将其称为 Thing Description Server 或 TD Server。TD Server 不需要是 Thing。特别是,TD 可以托管在普通 Web 服务器上,并通过其 URL 引用。
TD Server 可用于支持自描述。对于自描述, Thing 托管 其自身的 TD, 并通过由 URL 标识的 Web 资源使其可用。 不过,这样的 Web 资源不会作为 TD 本身中的 affordance 被包含。 该 Web 资源可以与 6.2 Well-Known URI 中定义的、作为 Introduction 机制使用的 well-known URL 相同,也可以不同。
安全传输的使用受 [wot-architecture11] 和 [wot-thing-description11] 规范的安全考虑和隐私考虑章节中给出的断言约束, 这些断言定义了推荐或强制使用安全传输,以及推荐使用相互认证的场景。
使用以下协议分发 TD 的 TD Server 受以下约束:
提供
TD 的基于
HTTP 的 TD Server
MUST 使用
GET 方法提供该资源。 提供
TD 的基于
HTTP 的 TD Server
的成功响应
MUST 具有 200 (OK) 状态,并在
body 中包含该 TD。
使用 JSON 序列化的成功
响应 MUST 在 Content-Type
header 中包含
application/json 或
application/td+json。 这里首选
application/td+json,因为它更具体,并且隐含
application/json。成功响应 body 的默认
序列化格式
MUST 为 JSON,并采用 JSON-LD 1.1
[JSON-LD11] 语法。
JSON-LD 语法允许语义扩展和处理。
提供
TD 的基于
HTTP 的
TD Server MAY
通过服务器驱动的内容协商提供替代表征,即遵循请求的
Accept 和 Accept-Encoding header,并使用受支持的 TD 序列化以及等价的
Content-Type 和 Content-Encoding header 进行响应。 此外,
按照 WoT Thing Description 1.1 规范中规范性定义的过程
[wot-thing-description11],
提供 TD 的基于
HTTP 的 TD Server
可以在服务器驱动的内容协商之后,使用不同的默认语言提供修改后的 TD 或
错误响应,即遵循请求的 Accept-Language header。
提供
TD 的基于
HTTP 的 TD Server
MUST 通过仅返回与对同一端点的
GET
请求所返回的 header 等价的 header,来响应
HEAD 请求。 这使客户端能够提前检索
Content-Length 等 HTTP header,以了解该 TD 的大小(以字节为单位),并决定高效的查询策略。
在受限环境中,单个 TD 对服务器或客户端来说可能过大而无法处理。 关于所请求 payload 的增量传输的特定协议建议,参见 10.1 增量 传输。
错误响应:
提供
TD 的基于
CoAP 的 TD Server
MUST 使用
GET 方法提供该资源。 提供
TD
的基于 CoAP 的 TD Server 的成功响应
MUST 具有 2.05 (Content) 状态,包含值为 50
(application/json)或 432
(application/td+json)的 Content-Format option,
并在 payload 中包含该 TD。
首选 Content-Format 432,因为它更具体,并且隐含
Content-Format 50。请注意,payload 可能会使用
block-wise transfer [RFC7959]
分拆到多个消息交换中。
提供
TD 的基于
CoAP 的
TD Server MAY
通过服务器驱动的内容协商提供替代表征,即遵循请求的
Accept option,并使用受支持的 TD 序列化和等价的
Content-Format option 进行响应。
提供 TD 的基于 CoAP 的 TD Server SHOULD 通过在其下一个响应中包含 TD 的大小估计值,来响应包含 Size2 option 的请求。 这在使用 block-wise transfer 获取 TD 时是相关的,并使客户端能够在总 payload 大小对其而言过大而无法处理时 中止检索。
在受限环境中,单个 TD 对服务器或客户端来说可能过大而无法处理。 关于所请求 payload 的增量传输的特定协议建议,参见 10.1 增量 传输。
错误响应:
Thing Description Directory(简称 TDD 或 Directory)是一个 Thing, 它提供用于 管理一组描述其他 Thing 的 TD 的服务。
如 图 4 所示,Thing Description Directory 可以包含零个或多个 TD。对于每个 TD,directory 会维护用于记录和搜索目的的额外元数据。这些元数据在 7.3.1.1 注册 信息和 7.3.1.3 匿名 TD 标识符中描述。一个 TD 如果在与 directory 的交互过程中嵌入这类额外元数据,则称为 Enriched TD。
Discovery 上下文中 TD 的本体已在
图 5 中介绍。
RegistrationInformation 类与存储在 directory 中的
TD
相关联。下表列出了注册信息属性,供嵌入或
引用 Discovery 上下文的
TD
使用。请注意,只有
Enriched
TD 会嵌入注册信息。一个
Enriched
TD MUST 在其
@context 中包含 URI
https://www.w3.org/2022/wot/discovery。
在此表中,client 指 TD 的生产者或消费者,
server 指 Thing
Description Directory。
每当使用 dateTime 表示绝对时间时,都 MUST 将其解释为 [RFC3339] 中规定的 date-time。 具体而言,时区偏移不是可选的。
我们应当更新本体,使其只使用 date-time 和 RFC3339,而不是这种奇怪的 dateTime 与 date-time 组合,但这也应在 TD 规范中 解决。不过,我们需要一个带有这一附加 限制的新 SHACL shape。
| 词汇术语 | 描述 | Client 赋值 | Server 赋值 | 类型 |
|---|---|---|---|---|
created |
提供该 TD 实例在 directory 内创建时的绝对时间。
这 MAY 由 directory 设置,并返回给消费者。 |
只读 | 可选 |
dateTime
|
modified |
提供该 TD 实例在 directory 内最后修改时的绝对时间。
这 MAY 由 directory 设置,并返回给消费者。 |
只读 | 可选 |
dateTime
|
expires |
提供该 TD 实例注册过期时的绝对时间。
生产者 MAY 设置 此项,以在注册期间指示绝对过期时间。 对于支持可过期 TD 的 server:如果
|
可选 | 可选 |
dateTime
|
ttl |
生存时间:从注册时间起到 TD
实例注册过期时为止的相对时间量,单位为秒。
生产者 MAY 设置 此项,以在注册期间指示相对过期时间。 对于支持可过期 TD 的 server:
server MUST 使用
|
可选 | 只读 | number |
retrieved |
从 server 检索 TD 时的绝对时间。
这对于打算处理其他绝对时间戳、 但没有内部时钟或其他获取当前时间手段的 client 很有用。 |
只读 | 可选 |
dateTime
|
生产者可以设置过期时间,以告知 directory 和其他消费者 TD 注册的有效性。 对于动态 TD 的过期,expiry 也是一个有用的指示器, 例如当地理位置或属性等元数据的变化预期仅在有限时间内有效时。 消费者可以依赖过期时间来了解检索到的 TD 将有效多久,以及何时 需要请求更新的版本。检索到已过期 TD 的消费者, 可以将其视为非活动 client 的元数据。
对于 server,过期时间有助于实现对过时或意外 注册的自动移除。Server SHOULD 定期清除已超过 其过期时间的 TD。 规定全局性的强制要求或 过期时间上限是特定于应用的,超出本规范范围。 Server 可以强制规定或设置可配置的过期时间上限, 并拒绝不符合要求的请求。Server 的清除操作在与无法显式注销其 TD 的 client(例如 IoT 设备)交互时尤其有益。这可能是由于 特定协议的限制、故障、损毁或非正常退役导致的。 此类 client 应设置一个合理较短的过期时间,并在正常运行期间 定期延长它。可以通过完全或部分更新注册来延长过期时间, 包括不对 TD 做任何更改的更新;参见 7.3.2.1.3 更新。 如果 client 停止运行,具有清除能力的 directory 将自动移除其 注册。
id,以允许本地
引用。 该本地
标识符
SHOULD 是 UUID Version 4,
并以 URN [RFC4122]
形式呈现。
UUID Version 4 是随机数或伪随机数,
不会携带有关 host 或
resource 的非预期信息。
Directory 服务提供对 System User Data 的访问,并且需要提供适当的安全和 隐私保护。WoT Directory Service API 实现中用于 真实性和机密性的安全传输协议和访问控制的使用, 受 [wot-architecture11] 中给出的 安全考虑和隐私考虑约束。
HTTP API 响应必须为成功响应和错误响应使用本节中
描述的适当状态码。
HTTP API MUST
使用 Problem Details
[RFC7807] 格式
在 HTTP 客户端错误(4xx)和服务器错误(5xx)响应中
携带错误详情。 这使机器和人类都能了解高层错误类别和
细粒度详情。所有使用
Problem Details 描述的 HTTP API 错误
响应 MUST 使用
UTF-8 编码。
如果 HTTP 请求中设置了
Accept-Language header field,HTTP API 错误响应
MAY 使用主动协商以不同语言
报告详情 [RFC7231]。
这些 API 按 [RFC7231] 第 6 节中的定义设置 HTTP 状态码。 使用的错误码列表包括(但不限于)以下内容:
WWW-Authenticate header 中。
对于
每个响应 GET
方法的 HTTP 端点,server MUST 接受
HEAD 请求并且只返回
header。 这允许 client 在不接收 body 的情况下检索
Content-Length 等 header,并决定合适的策略来查询信息。
例如,受限 client 可以只请求对象的
必要部分(使用适当的搜索查询),或以较小子集检索项目列表。
在受限环境中,单个 TD 对 server 或 client 来说可能过大而无法处理。 这会影响读取(即检索一个或多个 TD 或 TD 片段) 和写入(即提交 TD 或 Partial TD)操作。 关于 payload 的增量传输的特定协议建议,参见 10.1 增量 传输。
为确保数据通过 URL 安全传输,预期 client 和 server 都会对 与 URL 其余部分中的分隔符冲突的字符进行百分号编码/解码。 这些字符在 [RFC1738] 第 2.2 节中被定义为 不安全。 如果不安全字符出现在 URL 中,可能导致非预期行为, 例如,当 path 中包含的 resource ID 本身就是 URL, 或者出现在搜索 query string 中时。
directory API 包括强制、推荐和 可选特性。当 directory 因不支持推荐特性或 可选特性而无法回答请求时,它 SHOULD 通过返回适当的 HTTP 错误告知 client 缺少这些特性。 以下 示例可作为实现指南:
/search/sparql 端点),使用 404 (Not
Found)。
/things 端点上的
PATCH),使用 405 (Method Not
Allowed)。
使用 TD 中已经提供的翻译来修改 TD 默认语言的过程, 在 WoT Thing Description 1.1 规范中作了规范性描述 [wot-thing-description11]。 使用此过程,Directory server 可以在服务器驱动的内容协商之后, 使用不同的默认语言提供修改后的 TD 或其自身 TD, 即遵循请求的 Accept-Language header。
Things API 是在
/things 端点提供的 RESTful HTTP API,
提供用于创建、检索、更新、删除和列表(CRUDL)
TD 的接口。
该 API 的设计符合 [RFC7231]
和 [REST-IOT]。
HTTP API 遵循以下通用规则:
CRUDL 操作在以下章节中描述:
创建指在 directory 内注册新的 TD。
TD 对象按照 7.3.2.1.6 验证进行验证。请注意,TD 可以由其所描述的 Thing 生成,也可以不由它生成。 尤其对于 brownfield 设备,可能需要一个单独的 Discoverer 过程 或服务代表某个 Thing 生成并注册 TD。
具有
id 属性标识的 TD MUST
与没有标识符的 TD(Anonymous
TD)区别处理。 创建操作详述如下:
id 的 TD MUST 在 HTTP
PUT 请求的 body 中提交到 directory 的
/things/{id} 端点,其中
id 是唯一 TD 标识符,
存在于 TD 对象内部。 Anonymous
TD 会以不同方式处理;见下文。
请求
SHOULD 包含用于 TD 的 JSON 序列化的
application/td+json Content-Type
header。 TD
对象按照 7.3.2.1.6
验证进行验证。成功
处理后,server MUST 以 201 (Created)
状态响应。
注:如果目标位置对应于已有 TD, 则该请求应改为作为更新操作继续,并响应 适当的状态码(参见更新章节)。
具有标识符的 TD 的创建操作
在 7.3.2.4 API
规范(Thing Model) 中指定为
createThing action。
POST 请求的 body 中提交到 directory 的
/things
端点。 该
请求 SHOULD 包含用于 TD 的 JSON 序列化的
application/td+json Content-Type
header。 TD
对象按照 7.3.2.1.6
验证进行验证。Directory
MUST 为任何 Anonymous
TD 分配本地
标识符,以便从 directory 进行本地管理和检索。
系统生成 ID 的 scheme 在
7.3.1.3
匿名 TD 标识符中描述。成功
处理后,server MUST 以 201
(Created)
状态响应,并带有一个包含已创建 TD resource 的
系统生成 URI 的 Location header。
该系统生成 URI 包含 TD 的
系统生成标识符,并可随后用于从
directory 查询该 TD。
Anonymous
TD 的创建操作在
7.3.2.4 API
规范(Thing Model) 中指定为
createAnonymousThing action。
支持可过期 TD 的 server 将按
7.3.1.2
注册过期中描述的方式实现此类功能。
特别是,如果在创建期间给出
ttl(相对过期时间),此类 server 将计算并存储
expires 值。
必须使用 HTTP
GET 请求在 /things/{id}
端点执行已有 TD 的检索
MUST,
其中 id 是唯一 TD
标识符。 成功
响应
MUST 具有 200 (OK) 状态,并在
body 中包含所请求的 TD。 使用 JSON 序列化的成功
响应 MUST 在 Content-Type
header 中包含
application/json 或
application/td+json。 这里首选
application/td+json,因为它更具体,并且隐含
application/json。请注意,默认
序列化为带 JSON-LD 语法的 JSON,并且
可以协商替代序列化;参见
7.3.2.1 Things
API。
retrieve 操作在 7.3.2.4 API 规范(Thing
Model)中指定为
retrieveThing action。
以下是检索到的 TD 的示例:
这是一个 Enriched TD,它包含注册信息,例如 TD 在 directory 内的创建和修改时间。
下面的示例展示了一个检索到的 Anonymous
TD,它采用 Enriched
TD 形式,并具有本地标识符
urn:uuid:48951ff3-4019-4e67-b217-dbbf011873dc。
以下是检索到的 TD 的示例,该 TD 注册时使用了 3600 秒(一小时)的相对过期时间。server 已将 绝对过期时间计算为修改时间之后的一小时。
为便于阅读,本示例中的时间值设置为精确数字。 在现实设置中,时间值可以包含小数部分。
更新操作用于替换或部分 修改已有 TD。
更新操作如下所述:
PUT 请求提交到
/things/{id} 端点时,MUST 替换已有 TD,
其中 id 是已有
TD 的标识符。 该
请求
SHOULD 包含用于 TD 的 JSON 序列化的
application/td+json Content-Type
header。 TD
对象按照 7.3.2.1.6
验证进行验证。成功后,server
MUST 以 204 (No
Content) 状态响应。
此操作在 7.3.2.4 API
规范(Thing Model)中指定为
updateThing property。
支持可过期 TD 的 server 将按
7.3.1.2
注册过期中描述的方式实现此类功能。
如果在更新操作期间设置了 ttl
(相对过期时间),server 将计算并设置
expires(绝对过期时间)值。
注:如果目标位置不对应于已有 TD,
则该请求应改为作为创建操作继续,并响应
适当的状态码(参见创建章节)。换句话说,
HTTP PUT 请求同时作为
创建或更新操作。
PATCH 请求提交到
/things/{id} 端点时,已有 TD
MUST 被部分修改,
其中 id 是已有
TD 的标识符。 部分
更新 MUST 使用
[RFC7396] 中描述的 JSON merge patch
格式处理。
请求
MUST 包含
application/merge-patch+json
Content-Type header,用于 merge patch 文档的 JSON 序列化。
输入
MUST 采用 Partial
TD 形式,并符合原始 TD
结构。 如果输入包含出现在原始 TD 中的成员,
则会替换其值。如果某个成员没有出现在
原始 TD 中,则添加该成员。如果成员
被设置为 null 且出现在原始
TD 中,则移除该成员。具有对象
值的成员会递归处理。应用
修改之后,TD 对象按照
7.3.2.1.6
验证进行验证。成功后,
server MUST 以
204 (No Content) 状态响应。
此操作在
7.3.2.4 API
规范(Thing Model)中指定为
partiallyUpdateThing property。
支持可过期 TD 的 server 将按
7.3.1.2
注册过期中描述的方式实现此类功能。
在部分更新操作期间,如果生成的
TD 具有 ttl
(相对过期时间),server 将计算并
设置新的 expires(绝对过期时间)
值。
patch 操作对于高效延长使用
ttl(相对过期时间)
值的注册的过期时间尤其有用。这通常通过提交一个
空 merge patch 文档来完成,即空 JSON
对象。这实际上等价于执行一个不更新任何内容的
部分更新操作,但会触发重新计算 expires
(绝对过期时间)值。此过期功能
只有在 server 按
7.3.1.2
注册过期中的定义支持它时才有效。
以下示例是一个 merge patch 文档,
仅用于更新 TD 的 base 和
registration expires 字段:
删除操作 MUST 使用
HTTP
DELETE 请求在
/things/{id} 执行,其中 id 是
已有 TD 的标识符。 成功
响应 MUST 具有
204 (No Content) 状态。 retrieve 操作
在 7.3.2.4 API 规范(Thing
Model)中指定为 deleteThing property。
listing 端点提供从 directory 查询完整 TD 对象集合的不同方式。
在许多场景中,相比完整
TD 对象,更倾向于检索部分内容,因为只需要
元素的子集(例如所有 TD 的某个 property 的 id 和
href),并且可以
节省网络资源。Search API 允许
查询 TD 对象的部分内容;参见 7.3.2.3 Search
API。
directory MUST 允许使用 HTTP GET 请求在
/things 端点检索已有 TD。
成功
响应 MUST 具有
200 (OK) 状态,并在 body 中包含 TD 数组。
使用 JSON 序列化的成功
响应 MUST 在 Content-Type
header 中包含
application/json 或
application/ld+json。 这里首选
application/ld+json,因为它更具体,并且隐含
application/json。请注意,默认
序列化是带 JSON-LD 语法的 JSON,并且
可以协商替代序列化;参见
7.3.2.1 Things
API。
可能存在 client 需要以较小 TD 子集 检索集合的场景。虽然 Search API(7.3.2.3 Search API)确实提供了查询特定 范围的能力,但它可能不是最优的,也不够开发者友好。 server MAY 支持分页,以小子集返回 集合。 分页必须 基于以下规则:
limit query 参数设置为
正整数时,server MAY 响应一个 TD 子集,
其总数小于或等于所请求的
数量。next
Link
header [RFC8288],其中带有
下一个子集的 URL。 next link
MUST
包含生成相同数据集及其排序所需的所有参数,
尤其是初始请求中给定的相同
limit 参数,以及以
下一个子集开头为锚点的从零开始的 offset
参数。 该
link MUST 是绝对的,或
相对于 directory API 的 base URL。
此外,它可以包含排序或 session
管理所必需的额外参数。canonical Link header [RFC8288],指向
该集合,并包含一个 etag
参数来表示集合的当前状态。 该 link 可以是绝对的,也可以
相对于 directory API 的 base URL。
etag 值可以是修订号、
时间戳或 UUID Version 4,并在 TD
集合以影响 TD 排序的方式发生变化时设置。
client 可以依赖
etag 值来了解该
集合在分页检索过程中是否保持一致。
例如,TD 的创建或删除,或用于
排序的 TD 字段的更新,可能会移动计算出的分页窗口。
上述规范遵循 Linked Data Paging [LDP-Paging] 的一个子集,以允许 JSON-LD 数组的可选分页。 可以实现 Linked Data Paging 的其他部分,例如用于 遵循 client 的查询偏好,或添加其他 link relation 以进行语义 注解和替代导航链接。
以下示例提供了分页检索 TD 的演练:
作为 将 TD 数组作为响应 body 的替代方式, server MAY 发送 更详细的 payload,使 server 端信息(例如分页信息) 可以在实际数据之外一并包含。
替代分页格式的灵感来自 Hydra Advanced Concepts,更具体地说,是 Partial Collection View。根据我们的目的进行调整, 并使用 members 字段来容纳 TD 数组,listing 端点的形式如下:
为了告诉 server 发送哪种格式,
可以向请求添加额外 query 参数
?format=array|collection。
?format=array 是默认
参数,不必显式提供,并会产生由纯 TD 数组构成的
server 响应。
?format=collection 应产生
如 示例
9 中所述格式的
server 响应。
listing 操作在 7.3.2.4 API 规范(Thing
Model)中指定为
things property。
存储之前对
TD 对象进行句法验证是 RECOMMENDED 的,
以防止常见的错误
提交。 server
SHOULD 至少使用 [wot-thing-description11]
中定义的
Minimal Validation 来验证 TD,包括使用
WoT Thing Description (1.0) JSON Schema 或
WoT Thing Description 1.1 JSON Schema,以及
A. WoT Discovery TD 扩展的
JSON Schema 中定义的 JSON
schema,以便根据
@context 的值适当地验证 Enriched
TD。
可以添加额外形式的验证来
支持各种使用场景。例如,某个使用场景可能
需要对输入 TD 进行有状态验证,以确保
version 值根据预定义规则初始化并
更新。
如果 server 未能验证
TD 对象,它 MUST 用必要详情告知
client,以识别并
解决错误。
验证错误 MUST 描述为
Problem Details [RFC7807],
并带有名为
validationErrors 的扩展字段,
该字段设置为由具有 field 和
description 字段的对象组成的数组。 这
对以机器可读方式表示错误是必要的。
所有使用
Problem Details 描述的验证错误
响应 MUST 使用
UTF-8 编码。 如
在错误报告通用规则中已经说明的那样,
如果 HTTP 请求中设置了
Accept-Language header field,
验证错误响应可以使用主动协商以
不同语言报告详情 [RFC7231]。
示例 10 是一个 包含两个验证错误的错误响应示例。
Notification API 用于通知 client 有关 directory 内维护的 TD 的变更。Directory MAY 实现 Notification API。
Notification API MUST 遵循 Server-Sent Events(SSE)
[HTML]
规范,在
/events 端点向 client 提供事件。 特别是,
server 对成功请求以 200 (OK)
状态和 text/event-stream Content Type 响应。
重新连接的 client 可以通过将最后一个事件 ID 作为
Last-Event-ID
header 值提供,从最后一个事件继续。
server SHOULD 在每个事件中将事件 ID 作为
id 字段提供,并通过交付所有错过的
事件来响应重新连接的 client。
本节其余部分描述在 SSE 协议之上的实现 细节。使用 MQTT 等其他协议实现 notification 功能是可能的,并且可能在本规范未来 版本中形式化。
thing_created、
thing_updated 和
thing_deleted event type,
产生归因于 directory 内 Thing Description 生命周期的事件。server MUST 支持基于 client 在订阅时给出的 event type 进行事件过滤。
例如,给定 URI Template
/events{/type}:
/events/thing_created 指示
server 只交付类型为
thing_created 的事件
/events 指示 server
交付所有事件client 需要分别订阅,以便从 server
接收事件子集(例如仅
thing_created 和
thing_deleted)。使用
HTTP/2 时,同一
domain 上的多个订阅(HTTP stream)会在单个
连接上多路复用。
event data MUST 包含 event object 的 JSON 序列化。 event data object 根据请求可以是 Partial TD,也可以是完整 TD object:
diff query 参数设置为
true 且事件具有
thing_created 类型时,server
MAY 将完整 TD
object 作为 event data 返回。
diff query 参数设置为
true 且事件具有
thing_updated 类型时,server
MAY 按照 JSON Merge
Patch [RFC7396]
格式告知 client 更新的部分。
基于 JSON Merge
Patch [RFC7396] 的
thing_updated event data
MUST 始终包含 TD 的标识符,
无论它是否被
更改。
以下示例展示了对 示例 12 中的 TD 执行更新时触发的事件:
thing_deleted 事件,
diff query 参数 MUST 被忽略。
换句话说,当
diff 设置为 true 时,
server 不应在
thing_deleted 事件的 payload 中包含额外
property。
diff query 参数的 server 被请求使用
该 query 参数时,它应拒绝该请求。
这是为了在连接时告知 client 缺少此类
功能,并避免因缺失 event data
属性而导致运行时异常。
Notification API 在 7.3.2.4 API
规范(Thing Model) 中指定为三个 event
affordance,分别为:
thingCreated、thingUpdated 和
thingDeleted。
某些早期 SSE 实现(包括 HTML5 EventSource)不允许在初始 HTTP 请求中设置自定义 header。 Authorization header 在少数 OAuth2 flow 中是 必需的,而将其作为 query 参数传递是 不建议的。浏览器有 polyfill, 现代库也允许设置 Authorization header。
用于搜索 directory 的子 API,例如发出
查询。可能存在不同形式和级别的查询,
例如句法查询(JSONPath、XPath)与
语义查询(SPARQL),并且更高级的查询类型
可能并非所有 directory 都支持。因此,此 API
将有更多子章节,其中一些将是
可选的。Search 还包括用于管理
内容列表(例如由查询返回的内容)的子 API,
包括处理分页等。请注意,一种
特殊形式的查询将能够返回
所有内容。结果可能受请求者
授权约束。
待进一步讨论:到其他 TDD 的联邦查询、
空间和网络受限查询、Links
建议 directory 实现 search API,以根据 client 特定 查询高效提供 TD RECOMMENDED。
本节为非规范性内容。
对 JSONPath Search API 的支持是可选的。如果实现, JSONPath API 必须允许使用 HTTPGET 请求在
/search/jsonpath?query={query} 端点搜索 TD,
其中 query 是 JSONPath 表达式。
请求必须包含有效的 JSONPath
[JSONPATH]
作为搜索参数。成功响应必须具有
200 (OK) 状态,在 Content-Type header 中包含
application/json,
并在 body 中包含完整 TD 集合或
TD 片段集合。使用 JSONPath 的句法
搜索在 7.3.2.4 API 规范(Thing
Model)中指定为
searchJSONPath action。
本节为非规范性内容。
对 XPath Search API 的支持是可选的。如果实现, XPath query API 必须允许使用 HTTPGET 请求在
/search/xpath?query={query} 端点搜索 TD,
其中 query 是 XPath 表达式。该
请求必须包含有效的 XPath 3.1 [xpath-31] 作为
搜索参数。成功响应必须具有 200
(OK) 状态,在 Content-Type header 中包含
application/json,并在 body 中包含
JSON 序列化形式的查询响应。响应的数据 schema
由查询和
XPath 规范隐式定义。使用 XPath 的句法搜索
在 7.3.2.4 API 规范(Thing
Model)中指定为 searchXPath action。
GET 请求在
/search/sparql?query={query} 端点提交查询,
其中 query 是 SPARQL
表达式。
支持使用 HTTP POST 方法在
/search/sparql 端点进行 SPARQL
搜索是 OPTIONAL 的。
带有
SELECT 或
ASK query 的成功
请求 MUST 返回
200 (OK) 状态响应,并且默认在
Content-Type header 中包含
application/json。 带有
CONSTRUCT
和 DESCRIBE query 的
成功请求 MUST
返回 200 (OK) 状态响应,并且默认在
Content-Type header 中包含
application/ld+json。 带有不同于
SELECT、ASK、
CONSTRUCT 或 DESCRIBE
的任何 query 的请求
MUST 返回 400
(Bad Request) 响应。 使用 SPARQL 的语义搜索
在 7.3.2.4 API 规范(Thing
Model)中指定为 searchSPARQL action。
WoT Thing
Description
Directory MAY 在其
SPARQL query API 中实现
federation。 如果实现,
SPARQL API MUST 实现
SPARQL 1.1 Federated Query 标准
[sparql11-overview]。
这里以 Thing Model 的形式给出 Thing Description Directory 的 API 模板。该 Thing Model 是规范性的(除非另有说明),但不应被视为 实现 Thing Description Directory 或与其交互的 唯一参考。另请参阅 7.3.2 Directory Service API 中给出的规范。
此 Thing Model 中给出的 searchJSONPath 和
searchXPath affordance 不是规范性的,
仅供参考。
请注意,WoT TD 1.1 规范 [wot-thing-description11]
要求 ExpectedResponse class 的实例
(即与 form 中 key
response 对应的 JSON object)具有
contentType,即使 HTTP response 的 body
应为空。在这种情况下,
可以使用 application/x-empty 以确保
由下面 TM 生成的 TD 是有效的。
{
"@context": [
"https://www.w3.org/2022/wot/td/v1.1",
"https://www.w3.org/2022/wot/discovery"
],
"@type": [
"tm:ThingModel",
"ThingDirectory"
],
"title": "Thing Description Directory (TDD) Thing Model",
"version": {
"model": "1.0.0"
},
"base": "{{DIRECTORY_BASE_URL}}",
"tm:optional": [
"/actions/createThing",
"/actions/createAnonymousThing",
"/actions/retrieveThing",
"/actions/updateThing",
"/actions/partiallyUpdateThing",
"/actions/deleteThing",
"/actions/searchJSONPath",
"/actions/searchXPath",
"/actions/searchSPARQL",
"/events/thingCreated",
"/events/thingUpdated",
"/events/thingDeleted"
],
"properties": {
"things": {
"description": "Retrieve all Thing Descriptions",
"uriVariables": {
"offset": {
"title": "Number of TDs to skip before the page",
"type": "number",
"default": 0
},
"limit": {
"title": "Number of TDs in a page",
"type": "number"
},
"format": {
"title": "Payload format",
"type": "string",
"enum": [
"array",
"collection"
],
"default": "array"
}
},
"forms": [
{
"href": "/things{?offset,limit,format}",
"htv:methodName": "GET",
"response": {
"description": "Success response",
"htv:statusCodeValue": 200,
"contentType": "application/ld+json",
"htv:headers": [
{
"htv:fieldName": "Link"
}
]
},
"additionalResponses": [
{
"description": "Invalid query arguments",
"contentType": "application/problem+json",
"htv:statusCodeValue": 400
}
]
}
]
}
},
"actions": {
"createThing": {
"description": "Create a Thing Description",
"uriVariables": {
"id": {
"@type": "ThingID",
"title": "Thing Description ID",
"type": "string",
"format": "iri-reference"
}
},
"input": {
"description": "The schema is implied by the content type",
"type": "object"
},
"forms": [
{
"href": "/things/{id}",
"htv:methodName": "PUT",
"contentType": "application/td+json",
"response": {
"description": "Success response",
"htv:statusCodeValue": 201
},
"additionalResponses": [
{
"description": "Invalid serialization or TD",
"contentType": "application/problem+json",
"htv:statusCodeValue": 400
}
]
}
]
},
"createAnonymousThing": {
"description": "Create an anonymous Thing Description",
"input": {
"description": "The schema is implied by the content type",
"type": "object"
},
"forms": [
{
"href": "/things",
"htv:methodName": "POST",
"contentType": "application/td+json",
"response": {
"description": "Success response including the system-generated URI",
"htv:headers": [
{
"description": "System-generated URI",
"htv:fieldName": "Location"
}
],
"htv:statusCodeValue": 201
},
"additionalResponses": [
{
"description": "Invalid serialization or TD",
"contentType": "application/problem+json",
"htv:statusCodeValue": 400
}
]
}
]
},
"retrieveThing": {
"description": "Retrieve a Thing Description",
"uriVariables": {
"id": {
"@type": "ThingID",
"title": "Thing Description ID",
"type": "string",
"format": "iri-reference"
}
},
"output": {
"description": "The schema is implied by the content type",
"type": "object"
},
"safe": true,
"idempotent": true,
"forms": [
{
"href": "/things/{id}",
"htv:methodName": "GET",
"response": {
"description": "Success response",
"htv:statusCodeValue": 200,
"contentType": "application/td+json"
},
"additionalResponses": [
{
"description": "TD with the given id not found",
"contentType": "application/problem+json",
"htv:statusCodeValue": 404
}
]
}
]
},
"updateThing": {
"description": "Update a Thing Description",
"uriVariables": {
"id": {
"@type": "ThingID",
"title": "Thing Description ID",
"type": "string",
"format": "iri-reference"
}
},
"input": {
"description": "The schema is implied by the content type",
"type": "object"
},
"forms": [
{
"href": "/things/{id}",
"htv:methodName": "PUT",
"contentType": "application/td+json",
"response": {
"description": "Success response",
"htv:statusCodeValue": 204
},
"additionalResponses": [
{
"description": "Invalid serialization or TD",
"contentType": "application/problem+json",
"htv:statusCodeValue": 400
}
]
}
]
},
"partiallyUpdateThing": {
"description": "Partially update a Thing Description",
"uriVariables": {
"id": {
"@type": "ThingID",
"title": "Thing Description ID",
"type": "string",
"format": "iri-reference"
}
},
"input": {
"description": "The schema is implied by the content type",
"type": "object"
},
"forms": [
{
"href": "/things/{id}",
"htv:methodName": "PATCH",
"contentType": "application/merge-patch+json",
"response": {
"description": "Success response",
"htv:statusCodeValue": 204
},
"additionalResponses": [
{
"description": "Invalid serialization or TD",
"contentType": "application/problem+json",
"htv:statusCodeValue": 400
},
{
"description": "TD with the given id not found",
"contentType": "application/problem+json",
"htv:statusCodeValue": 404
}
]
}
]
},
"deleteThing": {
"description": "Delete a Thing Description",
"uriVariables": {
"id": {
"@type": "ThingID",
"title": "Thing Description ID",
"type": "string",
"format": "iri-reference"
}
},
"forms": [
{
"href": "/things/{id}",
"htv:methodName": "DELETE",
"response": {
"description": "Success response",
"htv:statusCodeValue": 204
},
"additionalResponses": [
{
"description": "TD with the given id not found",
"contentType": "application/problem+json",
"htv:statusCodeValue": 404
}
]
}
]
},
"searchJSONPath": {
"description": "JSONPath syntactic search. This affordance is not normative and is provided for information only.",
"uriVariables": {
"query": {
"title": "A valid JSONPath expression",
"type": "string"
}
},
"output": {
"description": "The schema depends on the given query",
"type": "object"
},
"safe": true,
"idempotent": true,
"forms": [
{
"href": "/search/jsonpath?query={query}",
"htv:methodName": "GET",
"response": {
"description": "Success response",
"contentType": "application/json",
"htv:statusCodeValue": 200
},
"additionalResponses": [
{
"description": "JSONPath expression not provided or contains syntax errors",
"contentType": "application/problem+json",
"htv:statusCodeValue": 400
}
]
}
]
},
"searchXPath": {
"description": "XPath syntactic search. This affordance is not normative and is provided for information only.",
"uriVariables": {
"query": {
"title": "A valid XPath expression",
"type": "string"
}
},
"output": {
"description": "The schema depends on the given query",
"type": "object"
},
"safe": true,
"idempotent": true,
"forms": [
{
"href": "/search/xpath?query={query}",
"htv:methodName": "GET",
"response": {
"description": "Success response",
"contentType": "application/json",
"htv:statusCodeValue": 200
},
"additionalResponses": [
{
"description": "XPath expression not provided or contains syntax errors",
"contentType": "application/problem+json",
"htv:statusCodeValue": 400
}
]
}
]
},
"searchSPARQL": {
"description": "SPARQL semantic search",
"uriVariables": {
"query": {
"title": "A valid SPARQL 1.1. query",
"type": "string"
}
},
"output": {
"description": "The schema depends on the given query",
"type": "object"
},
"safe": true,
"idempotent": true,
"forms": [
{
"href": "/search/sparql?query={query}",
"htv:methodName": "GET",
"response": {
"description": "Success response",
"contentType": "application/json",
"htv:statusCodeValue": 200
},
"additionalResponses": [
{
"description": "SPARQL query not provided or contains syntax errors",
"contentType": "application/problem+json",
"htv:statusCodeValue": 400
}
]
},
{
"href": "/search/sparql",
"htv:methodName": "POST",
"response": {
"description": "Success response",
"contentType": "application/json",
"htv:statusCodeValue": 200
},
"additionalResponses": [
{
"description": "SPARQL query not provided or contains syntax errors",
"contentType": "application/problem+json",
"htv:statusCodeValue": 400
}
]
}
]
}
},
"events": {
"thingCreated": {
"description": "Registration of Thing Descriptions inside the directory",
"uriVariables": {
"diff": {
"description": "Receive the full created TD as event data",
"type": "boolean"
}
},
"data": {
"title": "Partial/Full TD",
"type": "object"
},
"forms": [
{
"op": "subscribeevent",
"href": "/events/thing_created{?diff}",
"subprotocol": "sse",
"htv:headers": [
{
"description": "ID of the last event for reconnection",
"htv:fieldName": "Last-Event-ID"
}
],
"response": {
"contentType": "text/event-stream"
}
}
]
},
"thingUpdated": {
"description": "Updates to Thing Descriptions within the directory",
"uriVariables": {
"diff": {
"description": "Include TD changes inside event data",
"type": "boolean"
}
},
"data": {
"title": "Partial TD",
"type": "object",
"contentMediaType": "application/merge-patch+json"
},
"forms": [
{
"op": "subscribeevent",
"href": "/events/thing_updated{?diff}",
"subprotocol": "sse",
"htv:headers": [
{
"description": "ID of the last event for reconnection",
"htv:fieldName": "Last-Event-ID"
}
],
"response": {
"contentType": "text/event-stream"
}
}
]
},
"thingDeleted": {
"description": "Deletion of Thing Descriptions from the directory",
"data": {
"title": "Partial TD",
"type": "object"
},
"forms": [
{
"op": "subscribeevent",
"href": "/events/thing_deleted",
"subprotocol": "sse",
"htv:headers": [
{
"description": "ID of the last event for reconnection",
"htv:fieldName": "Last-Event-ID"
}
],
"response": {
"contentType": "text/event-stream"
}
}
]
}
}
}
安全是一个横切问题,需要在所有 WoT 构建块和 WoT 实现中加以考虑。 本章总结了一些通用问题和指南,以帮助维护具体 WoT discovery 实现的安全性。关于安全和隐私问题更详细、更完整的分析, 请参阅 WoT Security and Privacy Guidelines 规范 [WOT-SECURITY]。 WoT Thing 和 WoT TDD 也都是 Web 服务,应该使用 Web 服务的最佳实践来实现。 除了下面列出的具体安全考虑之外,还应评估 OWASP Top 10 [OWASP-Top-10] 等指南中讨论的安全 风险和缓解措施,并在适用时加以处理。
Directory 服务的某些功能,特别是 搜索查询,可能需要大量资源才能执行, 这一点可被用于对 WoT Thing Description Directory 服务发起拒绝服务(DoS) 攻击。在这种攻击中,WoT Directory 会因攻击者的请求而过载, 无法服务其他请求。
也可能使用 WoT Discovery 机制的元素, 对其他目标发起分布式拒绝服务(DDoS)攻击。 在这种攻击中,WoT Discovery 服务本身并不是目标。 相反,WoT Discovery 服务的某个方面会被利用, 以生成放大的网络流量,从而使实际目标——第三方——过载。 这种攻击有两个要求:第一,能够将流量重定向到 第三方;第二,存在一个可被利用来放大攻击者网络流量的 intermediary 服务。在某些协议中可以重定向网络流量, 例如通过修改 header 中的源信息来重定向未受保护的 CoAP。 放大可以通过利用三个乘数因子实现:请求与 响应 payload 大小的比率、在 CoAP 等协议中使用 "observe" (它可以为一个请求给出多个结果),以及使用 multicast (它可以允许多个 server 响应一个请求)。不支持认证的服务 是此类间接攻击的理想 intermediary。不幸的是,WoT Discovery 的 Introduction 机制本意就是提供开放访问机制以启动 discovery,因此可能会被用于这一目的。
在 LAN 上,证书和浏览器可能无法为 HTTPS 正确设置 TLS,因为浏览器期望证书指向一个公开可见的 URL。 在 LAN 内部使用 HTTP 是常见实践,但与 自描述结合时,这意味着 WoT Thing 基本上会让 TD 对所有 能访问该私有 LAN 的人可见。即使使用了 HTTP 密码等安全机制,在没有传输安全的情况下,这些机制也并不有效 (它们可以很容易被流量分析器发现)。
在 LAN 上如果可能, SHOULD 使用 PSK (pre-shared keys),即使用 [RFC4279] 中的某个 ciphersuite。 这确实要求为 Things 在一个共同的安全域中分配 PSK, 这通常通过遵循 onboarding 过程完成。不幸的是, 具体 onboarding 过程目前超出 WoT 规范的范围。
另一种选择是依赖本地网络安全 (即 WEP)。从安全或隐私角度看,这不是最佳解决方案, 但在某些上下文中可能可以接受。不过请注意, 所有能访问该网络的用户,反过来都可以通过 自描述访问所有 TD。如果 Things 无法通过 传输安全、认证和授权分别保护,则 SHOULD 设置一个单独的网络,即使用替代 SSID,并仅用于 IoT 设备。 使用 分段网络可以减少将此网络密码分发给需要访问连接到该网络的 IoT 设备集合的人员的需要。
另一种替代方案是使用位于云中的反向代理服务。 如果 IoT 设备可以访问云,则可以完成安全设置, 因为 proxy server 可以拥有公共 URL,初始连接可以使用 HTTPS, 然后通过 websocket 打开安全隧道。proxy 可以进一步重新暴露一个安全 端点,并可能添加认证。这种方法的缺点包括依赖外部云服务, 以及需要暴露外部访问点(这本身就是一种安全风险)。 第一个缺点可以通过在本地托管 proxy 服务并在本地 server 通过 ISP 连接时使用例如 dynamic DNS 暴露公共 URL 来解决。如果 Things 无法通过 传输安全、认证和授权分别保护,则可以通过能够提供合适访问控制的 proxy 使它们可供一般访问。
隐私是一个横切问题,需要在所有 WoT 构建块和 WoT 实现中加以考虑。 本章总结了一些通用问题和指南,以帮助维护具体 WoT discovery 实现的隐私性。关于安全和隐私问题更详细、更完整的分析, 请参阅 WoT Security and Privacy Guidelines 规范 [WOT-SECURITY]。
WoT discovery 架构通过采用两阶段方法,并允许在释放元数据之前强制执行 授权,从而避免依赖现有 discovery scheme 的隐私性。 然而,仍然存在若干隐私风险。下面列出了这些风险以及可能的 缓解措施。隐私风险的级别尤其取决于使用场景,以及是否存在这样一种风险: 与某个人有关的信息可能会以不符合该人隐私意愿的方式被分发。 对于隐私,我们区分以下几类广泛的使用场景:
事实上,所有这些场景都带有隐私风险。 即使在工厂自动化的情况下,也有可能捕获有关员工绩效的数据, 并且必须对这些数据进行适当管理。
在下文中,我们会频繁提到“tracking”。 该术语涵盖多种隐私风险,包括位置跟踪和行为画像。 一般而言,GDPR [GDPR-Defs] 第 4 条给出的“profiling”定义,应被视为等同于本文档中使用的 “tracking”。
在确立这些定义和类别之后,我们现在将讨论一些具体的隐私风险和潜在的 缓解措施。
Discovery 服务可能允许在未经某人同意的情况下确定其大致位置。 这种风险出现在某些特定情形中,可以避免或缓解。 它也类似于 DHCP 和 DNS 等其他网络服务带来的风险。
要发生这种风险,首先必须存在一个可与某人的位置可靠关联的 IoT 设备,例如必要的医疗设备或车辆。请注意, 该风险只适用于个人使用场景,而不适用于机构使用场景。 其次,该设备必须被配置为自动注册到最近的 directory 服务。 在这种情况下,可以根据 directory 服务的网络范围推断设备位置, 并根据设备位置推断该人的位置。
这有几种变体:
位置跟踪并不是唯一的画像风险。一般而言, “profiling”包括任何用于评估有关某人信息的机制, 包括经济状况、健康、偏好、兴趣、可靠性和行为。 如果所描述的 Thing 可以与某个人相关联, TD 中的一些元数据可用于推断此类信息。 下面的一些缓解措施也适用于这种更一般的画像定义。
其中一些风险与类似服务共有。例如, DCHP 会自动响应本地网络上的 IP 地址请求, 设备通常会在此过程中提供一个标识符(MAC 地址), 并且 DHCP server 会维护注册表。理论上, 能访问例如咖啡馆中 DHCP server 的人,可以使用这些信息跟踪某人的手机, 并推断其位置。
Directory 服务可能记录并跟踪某个人的查询, 通过其提供的认证身份识别该个人。然后, 与某个人相关联的一组查询可用于对该个人进行画像, 特定查询也可能泄露有关个人的个人信息。
访问公共 directory 时,像访问任何其他公共 Web 服务一样, 用户和实现应使用匿名身份提供者。特别是,OAuth2 可以 提供不识别具体个人的 token,它们只断言在其他地方证明的访问权限。
本节为非规范性内容。
TD 对象的 大小不受约束。单独或整体处理和传输它们可能会变得成本高昂。 对于受限设备而言,单个 TD 或 TD 列表可能过大,无法由其向消费者提供自身 TD、将其提交给 directory,或消费其他 TD。为了满足这些要求, server 应使用特定于协议的机制支持 payload 的增量传输:
chunked
Transfer-Encoding [RFC7230]
以增量方式接收和提供数据。
大多数 HTTP server 和 client 会自动处理以 chunk 传输的数据。 内存受限的 client 应考虑增量消费接收到的数据, 而不是尝试将整个对象加载到内存中进行反序列化。
将请求 IANA 将以下值分配到 [RFC8615] 中定义的 Well-Known URI。
wot将请求 IANA 将以下值分配到 [RFC6335] 中定义的 Service Name and Transport Protocol Port Number Registry。
wot_directory
subtype 表示 Thing Description Directory。将请求 IANA 将以下值分配到
[RFC6690]
中定义的 Constrained Restful Environments
(CoRE) Parameters registry 的 Resource Type(rt=)
Link Target Attribute Values 子注册表。
| 值 | 描述 | 引用 |
|---|---|---|
wot.thing |
Thing 的 Thing Description | 6.4 CoRE Link Format 和 CoRE Resource Directory |
wot.directory |
Thing Description Directory 的 Thing Description | 6.4 CoRE Link Format 和 CoRE Resource Directory |
将为 DID Documents 中使用的 Service Type 分配以下值。
| 值 | 描述 | 引用 |
|---|---|---|
WotThing |
Thing 的 Thing Description | 6.5 DID Documents |
WotDirectory |
Thing Description Directory 的 Thing Description | 6.5 DID Documents |
{
"title": "WoT Discovery TD-extensions Schema - 21 May 2021",
"description": "JSON Schema for validating TD instances with WoT Discovery extensions",
"$schema ": "https://json-schema.org/draft/2019-09/schema#",
"type": "object",
"properties": {
"registration": {
"type": "object",
"properties": {
"created": {
"type": "string",
"format": "date-time"
},
"expires": {
"type": "string",
"format": "date-time"
},
"retrieved": {
"type": "string",
"format": "date-time"
},
"modified": {
"type": "string",
"format": "date-time"
},
"ttl": {
"type": "number"
}
}
}
}
}
wot 用作 well-known URI 服务
名称。DirectoryDescription,以及用于标识仅链接到另一个 TD 的 TD
的类型 LinkDescription(这有助于引用远程 directory 以支持
federation)。
特别感谢 Arne Broering、Soumya Kanti Datta 和 Christian Bonnet 对 IoT 中现有 discovery 机制所做的调研工作。特别感谢 Philipp Blum、Victor Charpeney、Ben Francis、Christian Glomb、Daniel Peintner、 Christine Perey、Jan Romann 和 Elodie Thieblin 对本文档的 贡献。
非常感谢 W3C staff,尤其是 Kazuyuki Ashimura 和 Dominique Hazael-Massieux,以及 W3C Web of Things Interest Group(WoT IG)和 Working Group(WoT WG)的所有其他活跃 参与者,他们的支持、技术输入和建议促成了 本文档的改进。
Referenced in:
Referenced in:
Referenced in:
Referenced in: