9. 取消请求 (Canceling a Request)
前一节已经讨论了用于生成请求和处理所有方法的请求响应的通用UA行为。在本节中, 我们讨论一种通用方法, 称为CANCEL。
CANCEL请求, 顾名思义, 用于取消客户端先前发送的请求。具体来说, 它要求UAS停止处理该请求并对该请求生成错误响应。CANCEL对UAS已经给出最终响应的请求没有影响。因此, CANCEL最适用于服务器可能需要很长时间才能响应的请求。出于这个原因, CANCEL最适合INVITE请求, 因为INVITE请求可能需要很长时间才能生成响应。在这种使用场景中, 接收到INVITE的CANCEL请求但尚未发送最终响应的UAS会"停止振铃", 然后用特定的错误响应 (487) 响应INVITE。
CANCEL请求可以由代理和用户代理客户端构造和发送。第15节讨论了UAC在什么条件下会CANCEL INVITE请求, 第16.10节讨论了代理对CANCEL的使用。
有状态代理 (Stateful Proxy) 响应CANCEL, 而不是简单地转发它从下游元素接收到的响应。因此, CANCEL被称为"逐跳" (Hop-by-Hop) 请求, 因为它在每个有状态代理跳处得到响应。
9.1 客户端行为 (Client Behavior)
CANCEL请求不应该用于取消除INVITE之外的请求。
原因: 由于除INVITE之外的请求会立即得到响应, 为非INVITE请求发送CANCEL总是会产生竞态条件 (Race Condition)。
构造CANCEL请求
使用以下程序来构造CANCEL请求。CANCEL请求中的Request-URI、Call-ID、To、CSeq的数字部分和From头字段必须与被取消的请求中的相同, 包括标签 (Tag)。客户端构造的CANCEL必须仅具有单个Via头字段值, 该值与被取消的请求中的顶部Via值匹配。对这些头字段使用相同的值允许CANCEL与其取消的请求匹配 (第9.2节说明了如何进行这种匹配)。但是, CSeq头字段的方法部分必须具有CANCEL值。这允许它被识别和作为自己的事务处理 (参见第17节)。
如果被取消的请求包含Route头字段, 则CANCEL请求必须包含该Route头字段的值。
原因: 这是必需的, 以便无状态代理能够正确路由CANCEL请求。
CANCEL请求不得包含任何Require或Proxy-Require头字段。
发送CANCEL请求
一旦构造了CANCEL, 客户端应该检查它是否已收到被取消的请求 (在此称为"原始请求") 的任何响应 (临时或最终)。
- 如果未收到临时响应, CANCEL请求不得发送; 相反, 客户端必须等待临时响应到达后再发送请求。
- 如果原始请求已生成最终响应, 则不应该发送CANCEL, 因为它实际上是无操作 (No-op), 因为CANCEL对已经生成最终响应的请求没有影响。
当客户端决定发送CANCEL时, 它为CANCEL创建一个客户端事务, 并将CANCEL请求以及目标地址、端口和传输传递给它。CANCEL的目标地址、端口和传输必须与用于发送原始请求的相同。
原因: 如果允许在收到先前请求的响应之前发送CANCEL, 则服务器可能会在原始请求之前收到CANCEL。
事务完成
请注意, 对应于原始请求的事务和CANCEL事务将独立完成。但是, 取消请求的UAC不能依赖于接收原始请求的487 (Request Terminated) 响应, 因为符合RFC 2543的UAS不会生成此类响应。如果在64*T1秒内没有原始请求的最终响应 (T1在第17.1.1.1节中定义), 客户端应该将原始事务视为已取消, 并应该销毁处理原始请求的客户端事务。
9.2 服务器行为 (Server Behavior)
CANCEL方法请求服务器端的TU (事务用户) 取消挂起的事务。TU通过获取CANCEL请求, 然后假设请求方法是除CANCEL或ACK之外的任何方法, 并应用第17.2.3节的事务匹配程序来确定要取消的事务。匹配的事务就是要取消的事务。
服务器对CANCEL请求的处理取决于服务器的类型:
- 无状态代理 (Stateless Proxy) 将转发它
- 有状态代理 (Stateful Proxy) 可能响应它并生成一些自己的CANCEL请求
- UAS 将响应它
有关代理对CANCEL的处理, 请参见第16.10节。
UAS处理CANCEL
UAS首先根据第8.2节中描述的通用UAS处理来处理CANCEL请求。但是, 由于CANCEL请求是逐跳的并且不能重新提交, 因此服务器不能通过挑战它们来在Authorization头字段中获取正确的凭证。还要注意, CANCEL请求不包含Require头字段。
匹配事务
如果UAS根据上述程序未找到CANCEL的匹配事务, 它应该用481 (Call Leg/Transaction Does Not Exist) 响应CANCEL。
处理逻辑
如果原始请求的事务仍然存在, UAS在接收到CANCEL请求时的行为取决于它是否已经为原始请求发送了最终响应:
情况1: 已发送最终响应
如果已经发送, CANCEL请求对原始请求的处理没有影响, 对任何会话状态没有影响, 对为原始请求生成的响应也没有影响。
情况2: 未发送最终响应
如果UAS尚未为原始请求发出最终响应, 其行为取决于原始请求的方法:
- 如果原始请求是INVITE, UAS应该立即用487 (Request Terminated) 响应INVITE。
- 对于其他方法, CANCEL请求对本规范中定义的任何其他方法的事务处理没有影响。
响应CANCEL
无论原始请求的方法如何, 只要CANCEL匹配现有事务, UAS就会用200 (OK) 响应来响应CANCEL请求本身。此响应按照第8.2.6节中描述的程序构造, 注意对CANCEL的响应的To标签和对原始请求的响应的To标签应该相同。对CANCEL的响应被传递给服务器事务以进行传输。
CANCEL请求处理流程图
客户端发送CANCEL
↓
是否收到临时响应?
├─ 否 → 等待临时响应
└─ 是 → 构造CANCEL请求
├─ 复制头字段 (Request-URI, Call-ID, To, From, Via, Route)
├─ CSeq方法改为CANCEL
└─ 发送到与原请求相同的目的地
↓
服务器收到CANCEL
↓
匹配原始事务
├─ 未找到 → 返回481 (Call Leg/Transaction Does Not Exist)
└─ 找到
↓
已发送最终响应?
├─ 是 → 忽略CANCEL, 返回200 OK
└─ 否
├─ 如果是INVITE → 返回487 (Request Terminated) 给INVITE
└─ 其他方法 → 无影响
↓
返回200 OK给CANCEL
关键要点
-
CANCEL主要用于INVITE: 由于其他请求响应迅速, CANCEL对它们意义不大。
-
逐跳处理: CANCEL在每个有状态代理处被响应, 不会转发到下游。
-
必须等待临时响应: 客户端必须在收到临时响应后才能发送CANCEL, 以避免竞态条件。
-
独立事务: 原始请求和CANCEL请求是两个独立的事务, 分别完成。
-
200 OK响应: 服务器总是用200 OK响应CANCEL本身 (如果匹配到事务)。
-
487响应: 对于INVITE, 如果被取消, UAS应该返回487 (Request Terminated)。
-
头字段复制: CANCEL必须复制原始请求的大部分头字段以确保正确匹配。
本章小结:
第9章定义了CANCEL方法, 这是SIP中用于取消挂起请求的机制。CANCEL主要用于取消INVITE请求, 因为INVITE可能需要很长时间才能得到响应。CANCEL的关键特点是它是逐跳处理的, 并且必须在收到临时响应后才能发送, 以确保服务器已经知道原始请求。