调用 API 方法

API 中的版本控制由所谓的 TL 层提供支持。

添加新的对象构造函数或在构造函数中添加/删除字段的需求会给旧版本的 API 客户端带来向后兼容性问题。毕竟,仅仅更改模式中的构造函数也会更改其编号。为了解决这个问题,每个模式更新都被分离到一个单独的层中。

层是 TL 模式中更新后的方法或构造函数的集合。每一层都从 2 开始按顺序递增编号。第一层是基础层——没有任何更改的 TL 模式。

有一个辅助方法可以告知 API 客户端是否支持该层layer:

invokeWithLayer#da9b0d0d {X:Type} layer:int query:!X = X;

辅助方法invokeWithLayer只能与initConnection一起使用:当前层以及客户端的所有其他参数将被保存,任何后续请求都将使用此保存的值。详情请参见下文。

注意:在某些情况下,例如来自大型通道的更新,API 可能会返回来自较旧层的构造函数,这与连接的当前层不同。

客户端应将此视为服务器错误,并通过关闭并重新打开 TCP 套接字、使用initConnection500重新初始化会话并调用getDifference来处理它。

可用图层列表

保存客户信息

可以将当前客户端信息与授权密钥一起保存在服务器上。这有助于消除某些设备或特定语言版本在客户端遇到的问题,并避免在每个请求中发送层信息。

辅助方法`initConnection`接受客户端参数。在应用程序重启后首次调用 API 时,或者当某个参数的值发生更改时,必须调用此方法。

每次调用auth.bindTempAuthKey后,也必须调用initConnection。

调用此方法时,客户端当前使用的层也会被保存(使用initConnection被包装的层)。成功调用initConnection后,不再需要将每个 API 调用都包装在invokeWithLayerN中。

禁用更新

invokeWithoutUpdates#bf9459b7 {X:Type} query:!X = X;

invokeWithoutUpdates可用于在不订阅所用连接进行更新的情况下调用请求(默认情况下,文件查询启用此功能)。

顺序请求

默认情况下,服务器会以任意顺序处理并行请求。当客户端需要按特定顺序处理某些请求,并且打算在前一个请求完成之前发送新请求时,可以使用两种辅助方法。
这些方法可以降低需要严格顺序调用的延迟,因为客户端无需等待前一个方法调用的结果即可发送队列中的下一个请求,而是可以一次性发送所有请求(例如),每个请求都包含在一个invokeAfterMsg带有msg_id前一个请求返回值的回调函数中。

invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
invokeAfterMsgs#3dc4b4f0 {X:Type} msg_ids:Vector查询:!X = X;

例如,当客户端尝试发送因长时间等待网络连接恢复而累积的消息时,可以使用这种方法。在这种情况下,0xcb9f372d必须在每个请求的方法号之前添加一个 32 位数字,后跟一个 64 位消息标识符 msg_id,其中包含队列中前一个请求的信息。

第二种方法类似,只是它需要先成功处理几条消息,然后才能处理当前消息。

如果等待时间超过 0.5 秒(此值将来可能会更改)且未出现任何结果,则该方法将返回MSG_WAIT_TIMEOUT错误:通过重新发送请求来处理此错误,仍然包装在相同的invokeAfterMsg/invokeAfterMsgs构造函数中,并使用相同的id/ids。

msg_ids如果前面提到的任何查询msg_id失败(即查询发出 RPC 错误,包括FLOOD_WAIT_错误),则MSG_WAIT_FAILED当前请求将返回错误:处理此问题的最简单方法是强制执行本地同步,即在重新发送请求之前 等待所有先前msg_ids查询的响应。msg_id

只有当之前的任何请求也出现MSG_WAIT_FAILED/MSG_WAIT_TIMEOUT错误并需要重新发送时,才将当前请求包装在另一个invokeAfterMsg/invokeAfterMsgs构造函数中,并传入之前请求的新 ID,然后与当前请求一起重新发送。

情景一

为了更清楚地说明,假设查询序列如下:

  1. msg_id=1;messages.sendMessage message=a
  2. msg_id=2;invokeAfterMsg msg_id=1 (messages.sendMessage message=b)
  3. msg_id=3;invokeAfterMsg msg_id=2 (messages.sendMessage message=c)
场景 1.1

如果第一个 messages.sendMessage 查询msg_id=1失败,msg_id=2则msg_id=3后续的查询(包括带有 `--request-name` 和 `--request-name` 的查询)都会失败MSG_WAIT_FAILED,并且需要按如下方式重新发送。
要恢复呼叫队列,请发送以下新的查询序列:

  1. msg_id=4;messages.sendMessage message=b(重新发送旧查询msg_id=2)
  2. msg_id=5;invokeAfterMsg msg_id=4 (messages.sendMessage message=c)(重新发送旧查询msg_id=3)
场景 1.2

如果第一个 messages.sendMessage 查询msg_id=1成功,但第二个查询msg_id=2失败,则第二个查询msg_id=3将失败并返回错误MSG_WAIT_FAILED,需要按如下方式重新发送。
要恢复呼叫队列,请发送以下新的查询序列:

  1. msg_id=4;messages.sendMessage message=c(重新发送旧查询msg_id=3)

情景二

现在假设有以下不同的查询序列:

  1. msg_id=1;messages.sendMessage message=a
  2. msg_id=2;messages.sendMessage message=b
  3. msg_id=3;invokeAfterMsgs msg_ids=[1, 2] (messages.sendMessage message=c)

如果 messages.sendMessage 查询与msg_id=1and/ormsg_id=2失败,则与 and/or 相关的查询msg_id=3也会失败,MSG_WAIT_FAILED并且必须按如下方式重新发送。

要恢复呼叫队列,首先等待带有msg_id=1和 的查询的响应msg_id=2。

请注意这与场景 1 的不同之处,在场景 1 中我们无需等待先前查询的响应:这是因为在场景 1 中,每个invokeAfterMsg查询都直接等待一个先前的查询,队列中的任何新查询都链接到前一个查询,因此链中invokeAfterMsg任何查询的失败都会立即阻塞所有包含该查询的执行。然而,在本例中,我们同时等待两条消息,包含该查询的失败不会阻止包含该查询的执行,反之亦然;因此,当重新发送包含该查询的查询时,我们必须:msg_id=Nmsg_id <= N
msg_id=1msg_id=2msg_id=3

更简单的选择是始终遵循方案 1,永远不使用invokeAfterMsgs而只使用链式invokeAfterMsg调用,从而避免使用这种稍微复杂一些的恢复逻辑。

辅助方法序列

重要提示:如果将辅助方法invokeAfterMsg/invokeAfterMsgs与invokeWithLayerN或其他辅助方法一起使用,则invokeAfterMsg/invokeAfterMsgs必须始终是最外层的包装器。

数据压缩

我们建议在调用方法时使用 gzip 压缩,以减少网络流量。

协议文档中提供了模式和构造函数信息。

请求时的数据压缩

在发送查询之前,必须使用 gzip 对包含序列化高级查询主体(从方法编号开始)的整个字符串进行压缩。如果压缩后的字符串比原始字符串小,则可以发送gzip_packed构造函数。

传输二进制多媒体数据(照片、视频)或小消息(最多 255 字节)时,执行上述操作是没有意义的。

数据解压缩

默认情况下,服务器会根据上述规则压缩所有请求的响应以及更新。如果在 rpc_result 中收到gzip_packed构造函数作为响应,则必须提取并解压缩其后的字符串。然后,处理将基于生成的新字符串继续进行。