# 6. 锁定(Locking)

锁定资源的能力为序列化访问该资源提供了一种机制。使用锁,编写客户机可以提供合理的保证,确保其他主体在编辑资源时不会修改该资源。这样,客户端就可以防止“更新丢失”的问题。

本规范允许锁可以根据两个不同客户端指定的参数、涉及的主体数量(独占锁vs.共享锁)以及要授予的访问类型而变化。本文档仅为write访问类型定义了锁定。但是,该语法是可扩展的,并允许最终规范其他访问类型的锁定。

# 6.1 锁模型

本节为锁定行为提供了一个简明的模型。后面的章节将提供更多关于概念的细节,并提供一些典型声明语句作为参考。关系到LOCK和UNLOCK method处理的规范声明语句可以在具体讲解这些methods的章节中找到,而涵盖通用方法的规范语句会集中在这里。

  • 锁要么直接要么间接地锁定资源。

  • 当对一个资源的URL发出一个LOCK请求时,这个资源会被直接锁定并创建一个新锁。新锁的“锁根”就是这个URL。如果在请求时,URL没有映射到任何资源,一个新的空资源将被创建并直接锁定。

  • 排他锁(章节6.2)会与同一资源上的任何其他类型的锁发生冲突,无论其是直接锁还是间接锁。服务器不能在资源上创建冲突的锁。

  • 对于一个使用无限深度锁L来锁定的集合资源,它的所有成员资源都被间接锁定。该集合成员关系的改变会影响到被间接锁定的资源:

    • 如果一个成员资源被添加到集合中,新成员资源此时不能有冲突锁,因为它也将被L间接锁定。
    • 如果一个成员资源不再是集合的成员,那么这个资源必须不再被L间接锁定,也就是说应该被释放。
  • 每个锁由一个全局唯一的锁令牌(章节6.5)进行标识。

  • UNLOCK请求会删除带有指定令牌的锁。删除锁后,该锁不会再锁定任何资源。

  • 当锁令牌出现在“If”header中时,它就会在请求中被“提交”(第7节讨论了什么时候需要提交写入类型的锁令牌)。

  • 如果一个请求会导致任何锁的锁根成为一个未映射的URL,那么相应的锁也必须被这个请求删除。

# 6.2 排他锁与共享锁

锁的最基本形式是排他锁。排他锁避免了必须处理内容更改冲突,除了本规范中描述的方法之外,不需要任何其它协调。

但是,有时锁的目标不是排除其他人行使访问权,而是为主体提供一种机制,以表明它们打算行使自己的访问权。在这种情况下提供了共享锁。共享锁允许多个主体接收一个锁。因此,任何具有访问特权和有效锁的主体都可以使用锁定的资源。

使用共享锁,有两个影响资源的信任集。第一个信任集是由访问许可创建的,例如,受信任的多个主体都可以拥有向资源写入的权限。在那些拥有写入资源访问权限的主体当中,取得共享锁的主体集也必须相互信任,从而在写入权限许可集中创建一个(通常)较小的信任集。

从互联网上每一个可能的主体说起,在大多数情况下,这些主体中的绝大多数都没有给定资源的写访问权。对于少数拥有写访问权的主体来说,他们可能会决定通过使用排他写入锁来保证其编辑不会发生覆盖冲突。其他人可能会决定相信他们的合作者不会覆盖他们的工作(潜在的合作者是一组具有写权限的主体),并使用共享锁,这将会通知他们的合作者某个主体可能正在处理资源。

HTTP的WebDAV扩展不需要给主体提供所有必要的通信路径来协调其活动。当使用共享锁时,主体可以使用任何外来通信渠道来协调他们的工作(例如,面对面交流、书面笔记、屏幕上的便利贴、电话交谈、电子邮件等)。共享锁的目的是让协作者知道谁可能正在处理某个资源。

共享锁之所以存在是因为来自web分布式创作系统的经验表明,排他锁通常过于僵化和严格了。排他锁用于强制执行特定的编辑过程:取得排他锁、读取资源、执行编辑、写入资源、释放锁。这个编辑过程存在这样一个问题,即锁并不能一直被正确释放,例如,当一个程序出现问题或者当一个锁的创建者没有释放资源就离开时。虽然可以使用自动超时(章节6.6)和管理操作来移除有问题的锁,但这两种机制有可能在需要时偏偏都不可用:超时时间可能会等待很长,或者找不到管理员、管理员没空。

成功请求并取得一个新的共享锁,必须导致生成与该请求主体相关联的唯一锁。因此,如果5个主体对同一资源取得了共享写入锁,那么将有5个锁和5个锁令牌,每个主体一个。

# 6.3 所需支持

支持任何形式的锁定都不强求必须是一个WebDAV兼容资源。如果资源确实支持锁定,它可以选择为任何访问类型支持排他锁和共享锁的任意组合。

产生这种灵活性的原因是,锁定策略触及了各种存储库所使用的资源管理和版本控制系统的核心。这些存储库需要自己控制将提供何种类型的锁定。例如,一些存储库只支持共享写入锁,而其他存储库只支持排他写入锁,还有一些存储库根本不使用锁。由于每个系统都有足够多的不同,所以需要排除某些锁定特性,因此该规范将锁定作为WebDAV中唯一的协商轴。

# 6.4 锁定的创建者和权限

锁的创建者拥有使用锁来修改资源的特权。当修改锁定的资源时,服务器必须检查当前已验证的主体是否与锁定创建者匹配(此外还要检查是否提交了有效的锁定令牌)。

服务器可能允许除锁创建者之外的特权用户(例如,资源所有者或管理员)销毁锁。在[RFC3744]中定义了'unlock'权限来提供该实现。

服务器不需要接受来自所有用户或匿名用户的LOCK请求。

注意,拥有一个锁并不会授予修改锁定资源的完全特权。写访问和其他特权必须通过基本授权或身份验证机制来实施,而不是基于雾里看花的锁令牌来控制。

# 6.5 锁标记

锁令牌是一种标识特定锁的状态令牌。每个锁都有一个服务器生成的唯一的锁令牌。客户端不要试图以任何方式解析锁令牌。

锁令牌uri必须在所有资源中始终是唯一的。这种唯一性约束允许跨资源和服务器提交锁令牌,而不必担心混淆。由于锁令牌是唯一的,客户端可以在If头文件中提交锁令牌。

当一个LOCK操作创建了一个新的锁时,新的锁令牌会在10.5节中定义的锁令牌响应头中返回,也会在响应体中返回。

服务器可以使锁令牌公开可读(例如,在DAV:lockdiscovery属性中)。使锁令牌可读的一个用例是,资源所有者可以删除长期存在的锁(获得锁的客户端可能在清理锁之前崩溃掉了或者断开连接)。除非在用户指导下使用UNLOCK,否则客户端不应该使用另一个客户端实例创建的锁令牌。

本规范鼓励服务器为锁令牌创建统一唯一标识符(UUID),并使用由“统一唯一标识符(UUID) URN命名空间”([RFC4122])定义的URI形式。

然而,服务器可以自由地使用任何URI(例如来自另一个协议规范),只要它满足唯一性要求。例如,一个有效的锁令牌也可以使用附录c中定义的“opaquelocktoken”方案来构造。

# 6.6 锁定超时

一个锁可以有一个有限的生命周期。生命周期由客户机在创建或刷新锁时提出,但最终由服务器选择超时所需时长,以锁定过期前剩余的秒数来度量。

如果刷新锁请求成功,超时计数器必须重新启动(见9.10.2节)。在任何其他时间都不应该重新启动超时计数器。

如果超时过期,那么锁应该被移除。在这种情况下,服务器应该像有一个UNLOCK method被服务器执行了一样,使用超时锁的锁令牌在资源上通过其覆盖特权执行解锁方法。

建议服务器密切关注客户端提交的值,因为它们将指示客户端打算执行的活动类型。例如,在浏览器中运行的applet可能需要锁定资源,但是由于applet运行的环境不稳定,applet可能会在没有警告的情况下关闭。因此,applet很可能要求一个相对较小的超时值,这样如果applet死亡,锁就可以在较短时间内被自动清理。

但是,文档管理系统可能会要求非常长的超时时间,因为它的用户可能正在计划去脱机处理。

客户端不能仅仅因为已经过了超时时间,就假定锁已经立即被移除。

同样,客户端也不能因为超时时间还没到,就假定锁仍然存在。客户端必须假设锁可以在任何时间任意消失,而不管Timeout header中给出的值是多少。Timeout header仅反映服务器在无特殊情况下的行为。例如,拥有足够特权的用户可以在任何时候删除锁,或者系统可能会崩溃,从而丢失锁存在的记录。

# 6.7 发现锁定能力

由于服务器对锁的支持是可选的,所以当客户端需要去锁定服务器上的资源时,可以尝试请求锁定并希望得到最好的结果,或者执行某种形式的发现机制,以确定服务器支持哪些锁功能。这就是所谓的锁能力发现。客户机可以通过检索DAV:supportedlock属性来确定服务器支持的锁类型。

任何支持LOCK方法的DAV兼容资源都必须支持DAV:supportedlock属性。

# 6.8 主动发现锁

如果另一个主体锁定了某主体希望访问的资源,那么让该主体能够找出另一个主体是谁是很有用的。为此提供了DAV:lockdiscovery属性。此属性列出所有仍在发挥作用的锁,描述锁的类型,甚至可能提供锁令牌(供管理员手动解除)。

任何支持LOCK方法的WebDAV兼容资源都必须支持DAV:lockdiscovery属性。