--- title: "应用服务 API" weight: 30 type: docs --- {{< boxes/warning >}} 本页面的翻译未经核对,可能存在翻译质量不佳、错翻、漏翻等情况。您可以在 Forgejo 存储库 打开 Issue、提交 Pull Request 或邮件联系我们提出改进建议和参与翻译与核对。 {{< /boxes/warning >}} Matrix 客户端-服务器 API 与服务器-服务器 API 提供了实现一致的、自包含的去中心化消息结构的手段。然而,它们在实现 Matrix 中自定义服务器端行为(例如网关、过滤器、可扩展钩子等)方面手段有限。应用服务 API (AS API) 定义了一套标准 API,不受底层主服务器实现的影响,以便实现这些可扩展功能。 ## 应用服务 应用服务为被动组件,只能观察来自主服务器的事件。它们可以向参与的房间注入事件,但无法阻止事件的发送,也不能修改事件内容。为了观察来自主服务器的事件,主服务器需要配置,将某些类型的流量传递给应用服务。要实现这一点,需要手动在主服务器配置中加入应用服务的相关信息。 ### 注册 {{% boxes/note %}} 此前,应用服务曾可通过 HTTP API 注册到主服务器。这一机制因安全风险被移除。被攻陷的应用服务可重新注册全局 `*` 正则,从而窃取主服务器上*所有*流量。为防止此类风险,现在应用服务必须通过配置文件进行注册,并将这些配置文件与主服务器的配置文件关联。添加配置文件后,主服务器管理员可对注册过程中的可疑正则表达式做合理性检查。 {{% /boxes/note %}} 应用服务注册一组用户 ID、房间别名和房间 ID 的“命名空间”。如果某事件与任何命名空间匹配,则称该应用服务对该事件“感兴趣”。 应用服务还可以声明是否应由其独占管理指定命名空间,这称为“独占”命名空间。独占命名空间会阻止用户及其它应用服务在该命名空间内创建或删除实体。通常,当房间映射到另一个服务的真实房间(如 IRC)时,会使用独占命名空间;当应用服务仅对房间本身提供增强功能(如记录或搜索功能)时,可使用非独占命名空间。 注册信息以一组键值对表示,通常编码为 YAML 文件中的对象。其结构如下: {{% definition path="api/application-service/definitions/registration" %}} 独占的用户和别名命名空间应在符号后以下划线开头,以避免与主服务器的其他用户发生冲突。应用服务还应尽量在保留命名空间中体现其所代表的具体服务。例如,`@_irc_.*` 适合作为处理 IRC 的应用服务注册命名空间。 以下是用于 IRC 桥接应用服务的注册文件示例: ```yaml id: "IRC Bridge" url: "http://127.0.0.1:1234" as_token: "30c05ae90a248a4188e620216fa72e349803310ec83e2a77b34fe90be6081f46" hs_token: "312df522183efd404ec1cd22d2ffa4bbc76a8c1ccf541dd692eef281356bb74e" sender_localpart: "_irc_bot" # 最终为 @_irc_bot:example.org namespaces: users: - exclusive: true regex: "@_irc_bridge_.*" aliases: - exclusive: false regex: "#_irc_bridge_.*" rooms: [] ``` {{% boxes/note %}} 针对 `users` 命名空间,应用服务只能对*本地*用户注册感兴趣(即,用户 ID 以本地主服务器的 `server_name` 结尾)。影响其他主服务器用户的事件不会发送给应用服务,即便这些用户恰好匹配 `users` 命名空间(当然,除非事件影响的房间本身就属于应用服务感兴趣范围,例如该房间中还有另一个应用服务关心的用户)。 对于 `rooms` 和 `aliases` 命名空间,所有匹配房间中的事件都会被发送给应用服务。 {{% /boxes/note %}} {{% boxes/warning %}} 如果某主服务器上存在多个应用服务,则每个应用服务的 `as_token` 和 `id` *必须*唯一,这些项用于标识不同应用服务。主服务器*必须*强制执行此要求。 {{% /boxes/warning %}} ### 主服务器 -> 应用服务 API #### 授权 {{% changed-in v="1.4" %}} 主服务器在向应用服务发出请求时,*必须*包含 `Authorization` 头,内容为注册文件中的 `hs_token`。应用服务*必须*校验所提供的 `Bearer` 令牌与其已知的 `hs_token` 是否匹配,若不匹配则以 `M_FORBIDDEN` 错误拒绝请求。 `Authorization` 头的格式类似于 [客户端-服务器 API](/client-server-api/#client-authentication): `Bearer TheHSTokenGoesHere`. {{% boxes/note %}} 在本规范的早期版本中,曾使用 `access_token` 查询参数。服务器仅在兼容旧版本规范时需发送此查询参数。 如需发送 `query_string`,建议与 `Authorization` 头一同发送,以获得最大兼容性。 若两者均提供,应用服务应校验其值是否一致。 {{% /boxes/note %}} #### 旧路由 早期规格的应用服务 API 混合定义了多种在实际部署中被采用的端点。当前应用服务规范对所有端点定义了版本,以便与 Matrix 其他规范及未来更好兼容。 主服务器在与应用服务通信时应优先尝试当前规范指定的端点,但若应用服务返回非成功的 HTTP 状态码(如 404、500、501 等),则主服务器应回退到旧端点。 旧端点和当前端点拥有完全相同的请求体和响应格式,只是路径不同。各端点的对应关系如下: - `/_matrix/app/v1/transactions/{txnId}` 回退到 `/transactions/{txnId}` - `/_matrix/app/v1/users/{userId}` 回退到 `/users/{userId}` - `/_matrix/app/v1/rooms/{roomAlias}` 回退到 `/rooms/{roomAlias}` - `/_matrix/app/v1/thirdparty/protocol/{protocol}` 回退到 `/_matrix/app/unstable/thirdparty/protocol/{protocol}` - `/_matrix/app/v1/thirdparty/user/{user}` 回退到 `/_matrix/app/unstable/thirdparty/user/{user}` - `/_matrix/app/v1/thirdparty/location/{location}` 回退到 `/_matrix/app/unstable/thirdparty/location/{location}` - `/_matrix/app/v1/thirdparty/user` 回退到 `/_matrix/app/unstable/thirdparty/user` - `/_matrix/app/v1/thirdparty/location` 回退到 `/_matrix/app/unstable/thirdparty/location` 主服务器应定期重试新版端点,因为应用服务可能已经升级。 #### 未知路由 如果收到对不受支持(或未知)端点的请求,服务器必须响应 404 `M_UNRECOGNIZED` 错误。 同样,对于已知端点但不支持的 HTTP 方法,应使用 405 `M_UNRECOGNIZED` 错误指示。 #### 推送事件 应用服务 API 提供事务接口以发送事件列表。每批事件都包含事务 ID,其运行机制如下: ``` 正常情况 HS ---> AS : 主服务器以事务 ID T 发送事件。 <--- : 应用服务返回 200 OK。 ``` ``` AS ACK 丢失 HS ---> AS : 主服务器以事务 ID T 发送事件。 <-/- : AS 200 OK 丢失。 HS ---> AS : 主服务器以相同事务 ID T 重试。 <--- : 应用服务返回 200 OK。若 AS 已处理过这些事件,可对请求 NO-OP(它可通过事务 ID 判断事件是否相同)。 ``` 发送给应用服务的事件应为线性处理(仿佛来自事件流)。主服务器*必须*维护一个待发送给应用服务的事务队列。如果无法连接到应用服务,主服务器*应*指数退避,直至应用服务恢复可达。由于应用服务无法*修改*这些事件,主服务器可异步处理这些请求,不阻塞其它功能。主服务器在重试同一事务 ID 时*不得*修改(如增加额外)要发送的事件,因为应用服务可能已经处理过这些事件。 {{% http-api spec="application-service" api="transactions" %}} ##### 推送临时数据 {{% added-in v="1.13" %}} 若在[注册文件](#registration)中启用了 `receive_ephemeral` 设置,主服务器*必须*通过事务 API,通过请求体的 `ephemeral` 属性向应用服务发送与其相关的临时数据。此属性为数组,实际为客户端-服务器 [`/sync`](/client-server-api/#get_matrixclientv3sync) API 的 `presence` 和 `ephemeral` 部分的组合。 当前共可向应用服务传递三种事件类型: - **[`m.presence`](/client-server-api/#mpresence)**:如上下文要求,*必须*发送。例如,为与应用服务共享房间的用户,或匹配应用服务命名空间的用户发生的状态更新。 - **[`m.typing`](/client-server-api/#mtyping)**:应用与普通事件相同的规则,*必须*发送。即应用服务需注册房间本身或对房间内某用户感兴趣。数据格式应与客户端-服务器 API 相同,但需在顶层增加 `room_id` 字段,指明事件所属房间。 - **[`m.receipt`](/client-server-api/#mreceipt)**:应用与普通事件相同的规则,*必须*发送。数据格式与客户端-服务器 API 相同,并需在顶层增加 `room_id` 字段。针对[私有已读回执](/client-server-api/#private-read-receipts),只需针对匹配应用服务命名空间的用户发送。普通已读回执和线程已读回执则总会发送。 #### 心跳机制 {{% added-in v="1.7" %}} 应用服务 API 包含心跳(ping)机制,以便应用服务确保主服务器能访问自身。应用服务可利用此机制检测错误配置并适当报告。 实现时应注意,遇到临时性故障(如应用服务先于主服务器启动)不应导致完全失效,而应平滑处理。 机制如下(为简洁省略了可读性错误信息): **正常情况** ``` AS ---> HS : /_matrix/client/v1/appservice/{appserviceId}/ping {"transaction_id": "meow"} HS ---> AS : /_matrix/app/v1/ping {"transaction_id": "meow"} HS <--- AS : 200 OK {} AS <--- HS : 200 OK {"duration_ms": 123} ``` **`hs_token` 错误** ``` AS ---> HS : /_matrix/client/v1/appservice/{appserviceId}/ping {"transaction_id": "meow"} HS ---> AS : /_matrix/app/v1/ping {"transaction_id": "meow"} HS <--- AS : 403 Forbidden {"errcode": "M_FORBIDDEN"} AS <--- HS : 502 Bad Gateway {"errcode": "M_BAD_STATUS", "status": 403, "body": "{\"errcode\": \"M_FORBIDDEN\"}"} ``` **无法连接至应用服务** ``` AS ---> HS : /_matrix/client/v1/appservice/{appserviceId}/ping {"transaction_id": "meow"} HS -/-> AS : /_matrix/app/v1/ping {"transaction_id": "meow"} AS <--- HS : 502 Bad Gateway {"errcode": "M_CONNECTION_FAILED"} ``` `/_matrix/app/v1/ping` 端点如上说明,[`/_matrix/client/v1/appservice/{appserviceId}/ping`](#post_matrixclientv1appserviceappserviceidping) 端点见下文客户端-服务器 API 扩展部分。 {{% http-api spec="application-service" api="ping" %}} #### 查询 应用服务 API 包含两个查询 API:房间别名与用户 ID。应用服务*应*在处理查询请求时自行创建所查询的实体。如果应用服务愿意,可主动创建实体。在此过程中,主服务器会阻塞直至实体被创建并配置完成。若主服务器未收到该请求的响应,应重试数次,最终超时。这样,发起请求的客户端会收到 HTTP 408 “请求超时”。 {{% boxes/rationale %}} 阻塞主服务器,并让应用服务通过客户端-服务器 API 创建实体,比返回 initial sync 风格的 JSON 数据让主服务器初始化房间/用户更简单灵活。同时,也无需建立“回传通道”通知应用服务有关实体的信息,如房间 ID 到别名的映射。 {{% /boxes/rationale %}} {{% http-api spec="application-service" api="query_user" %}} {{% http-api spec="application-service" api="query_room" %}} #### 第三方网络 应用服务可通过向主服务器注册时的配置声明所支持的协议。这些网络通常为应用服务管理的第三方服务(如 IRC)。应用服务可为其注册协议填充 Matrix 房间目录,如客户端-服务器 API 扩展部分定义。 每种协议可包括若干“位置”(Location,亦称“第三方位置”或 “3PL”)。协议中的“位置”通常是第三方网络中的某处,如 IRC 频道。第三方网络上的用户也可由应用服务表示。 位置和用户可根据应用服务定义的字段检索,如显示名或其他属性。当客户端请求主服务器在特定“网络”(协议)中搜索时,搜索字段会被传递给应用服务进行筛选。 {{% http-api spec="application-service" api="protocols" %}} ### 客户端-服务器 API 扩展 应用服务可通过向主服务器表明自身身份,使用更强大的客户端-服务器 API 版本。 本节定义的端点主服务器*必须*在客户端-服务器 API 中仅向应用服务开放。 #### 身份声明 客户端-服务器 API 通过每个请求中的 `access_token` 推断用户 ID。为避免应用服务需为每个用户维护 access_token,应用服务应同时以其 `as_token` 作为 `access_token`,并声明希望以哪一用户(属于应用服务命名空间的用户)伪装身份访问。 输入项: - 应用服务令牌(`as_token`) - 要模拟的应用服务命名空间内的用户 ID 注意事项: - 适用客户端-服务器 API 的所有方面,除帐户管理。 - 把 `as_token` 赋值给 `access_token`,即通常客户端令牌所在的位置,如查询参数或 `Authorization` 头。这有助于应用服务复用客户端 SDK。 - 推荐通过 `Authorization` 头提供 `access_token`,避免其出现在 HTTP 请求日志中。 应用服务可通过在请求 URL 中追加 `user_id` 查询字符串参数,指定虚拟用户。该 user_id 必须在应用服务的 `user` 命名空间范围内。若缺省,则主服务器假定应用服务希望以注册文件中 `sender_localpart` 所指定用户的身份操作。 请求示例: GET /_matrix/client/v3/account/whoami?user_id=@_irc_user:example.org Authorization: Bearer YourApplicationServiceTokenHere #### 时间戳调整 {{% added-in v="1.3" %}} 应用服务可以修改事件关联的时间戳,从而更准确地反映事件的“真实”发送时间。这不会影响事件在服务器端的顺序,但能更好地反映(如桥接服务因对方网络有延迟而希望为消息打上原始时间)的实际时间。 以应用服务身份认证请求时,可追加 `ts` 查询参数以变更结果事件的 `origin_server_ts`。如时间戳不被 `origin_server_ts` 接受,服务器应以错误请求拒绝。 如未指定,则服务器行为不变:以服务器本地系统时间打时间戳,视为“当前”。 `ts` 查询参数仅以下端点有效: * [`PUT /rooms/{roomId}/send/{eventType}/{txnId}`](/client-server-api/#put_matrixclientv3roomsroomidsendeventtypetxnid) * [`PUT /rooms/{roomId}/state/{eventType}/{stateKey}`](/client-server-api/#put_matrixclientv3roomsroomidstateeventtypestatekey) 其它端点(如 `/kick`)不支持 `ts`,如需类似行为请用 `PUT /state` 端点模拟。 {{% boxes/warning %}} 变更事件时间不会改变事件在服务器端(DAG)顺序。事件仍会像以“当前时间”发送那样添加到 DAG 顶端。未来的 MSC,如 [MSC2716](https://github.com/matrix-org/matrix-spec-proposals/pull/2716),预计会提供更彻底的 DAG 顺序操作功能(如历史导入等)。 {{% /boxes/warning %}} #### 服务器管理员级权限 主服务器需赋予应用服务对其命名空间内所有用户和房间别名“完全控制”权限。这意味着应用服务应能管理命名空间内任意用户和房间别名。无需对房间别名控制权作额外 API 更改。 创建用户需要对 API 作如下调整: - 绕过验证码。 - 支持“无密码”用户。 为此需完全绕过注册流程。方法是在 `/register` 请求中附带 `as_token`,以及登录类型 `m.login.application_service`,从而指定无需密码的目标用户 ID。 POST /_matrix/client/v3/register Authorization: Bearer YourApplicationServiceTokenHere 内容: { type: "m.login.application_service", username: "_irc_example" } 同样,应用服务以用户身份登录,也要允许在无需该用户密码的情况下完成。具体方法是在 `/login` 请求中附带 `as_token`,并指定登录类型 `m.login.application_service`: {{% added-in v="1.2" %}} POST /_matrix/client/v3/login Authorization: Bearer YourApplicationServiceTokenHere 内容: { type: "m.login.application_service", "identifier": { "type": "m.id.user", "user": "_irc_example" } } 应用服务如试图*超出*其定义命名空间创建用户或别名,或以超出命名空间的用户登录,将会收到 `M_EXCLUSIVE` 错误码。 同理,普通用户试图在*应用服务定义的*命名空间内创建用户或别名(且该命名空间为独占)同样会收到 `M_EXCLUSIVE` 错误码。 若带 `m.login.application_service` 登录类型的 `/register` 或 `/login` 请求未附带有效 `as_token`,将返回 `M_MISSING_TOKEN` 或 `M_UNKNOWN_TOKEN` 错误码,HTTP 状态码为 401。其行为与客户端-服务器 API 的无效认证相同(参见[使用访问令牌](/client-server-api/#using-access-tokens))。 #### 心跳 {{% added-in v="1.7" %}} 这是[心跳机制](#pinging)的客户端-服务器 API 对应端点。 {{% http-api spec="client-server" api="appservice_ping" %}} #### 使用 `/sync` 和 `/events` 希望使用客户端-服务器 API 的 `/sync` 或 `/events` 端点的应用服务,*必须*以虚拟用户身份访问(通过查询字符串提供 `user_id`)。建议应用服务通过推送事务的方式处理事件,而非以 `sender_localpart` 标识的用户同步。 #### 应用服务房间目录 应用服务可为其定义的第三方协议维护独立的房间目录。这些房间目录可通过客户端-服务器 API 的 `/publicRooms` 端点及额外参数由客户端访问。 {{% http-api spec="client-server" api="appservice_room_directory" %}} ### 引用来自第三方网络的消息 应用服务应在所发送事件的 `content` 中包含 `external_url` 字段,用于指示消息来源。此字段主要用于桥接其它网络(如 IRC)的应用服务,通常会提供 HTTP URL 以供引用。 如果 `external_url` 存在,客户端应为用户提供访问该 URL 的方式。同时,客户端应确保 URL 的协议为 `https` 或 `http` 后再使用。 事件中出现 `external_url` 并不必然意味着事件来自应用服务。客户端在使用该 URL 时应保持警惕,因其可能不是事件来源的合法引用。