6. 锁定
具有锁定资源这种能力为客户端提供了一种序列化访问该资源的机制. 作者客户端在使用锁后, 可以合理地保证另一个主体在编辑资源时不会修改这个资源. 通过这种方式, 客户端可以防止 "丢失更新" 问题.
本规范允许锁根据两个客户端指定的参数而变化, 即涉及的主体数量 (互斥 vs. 共享) 和要授予的访问类型. 本文仅为一种访问类型 (写入) 定义锁. 但语法是可扩展的, 并允许最终指定其他访问类型的锁.
6.1. 锁模型
本章提供锁行为的简洁模型. 后续部分将参考这些模型语句并更详细地讨论一些概念. 与 LOCK 和 UNLOCK方法处理相关的规范性可以在它们相关章节部分找到, 而涵盖任一方法的规范性陈述将汇总在这里.
- 一个锁可以直接或间接的锁定一个资源.
- 当对资源的 URL 发出 LOCK 请求以创建新的锁时,资源将被直接锁定. 新锁的 "锁根(lock-root)" 是该 URL. 如在请求时 URL 没有映射到资源, 一个新的空资源将被创建并被直接锁定.
- 互斥锁 (第 6.2 节) 与同一资源上的任何其他类型的锁 (无论是直接还是间接) 都会发生冲突. 服务器不得[MUST_NOT]在资源上创建互相冲突的锁.
- 对使用深度无限锁
L
锁定的集合, 所有成员资源都会被间接锁定. 此类集合成员的关系变更会影响间接锁定资源的集合: - 每个锁都由单一的全局唯一锁令牌(第 6.5 章) 标识.
- UNLOCK 请求会删除具有指定锁定令牌的锁. 删除锁定后,没有资源再会被该锁锁定.
- 当锁定令牌出现在 "If"标头中时,就被认为是 "提交" 了该锁定令牌(第 7 章中 "写锁" 讨论了何时需要为写锁提交令牌).
- 如果请求会导致任何锁的锁根变为未映射的 URL, 那么该请求也必须[MUST]删除该锁.
6.2. 互斥锁 vs 共享锁
锁最基本的形式是互斥锁. 互斥锁避免了处理内容中需要修正冲突时, 不需要除了本规范中描述的方法之外的任何协调机制.
然而,有时锁的目标并不是排除其他人的访问权限, 而是提供一种机制, 供主体指示其访问权限. 共享锁因此被提出. 共享锁允许多个主体接收锁. 因此, 任何具有访问权限和有效锁的主体都可以使用被 (共享锁)锁定的资源.
使用共享锁时, 存在影响资源的两个信任集(trust sets). 第一个信任集由访问权限创建. 受信任的主体可能具有写入资源的权限. 在那些具有写入资源权限的人中, 已获取共享锁的主体也必须互相信任, 从而在访问权限写入集合内创建一个 (通常) 较小的信任集.
从互联网上每个可能的主体开始, 大多数情况下, 这些主体中的大多数不具有写入给定资源的访问权限. 在具有写入访问权限的少数主体中, 某些主体可能决定使用互斥写入锁来保证他们的编辑不会被覆盖. 另一些可能会决定相信他们的合作者不会覆盖他们的工作 (潜在的合作者是具有写入许可的主体集), 并使用共享锁, 这样可以通知他们的合作者, 某个主体可能正在对资源进行操作.
HTTP 的 WebDAV 扩展不需要提供所有主体协调其活动所需的通信路径 (communications paths). 在使用共享锁时,主体可以使用任何带外的通信通道 (out-of-band communication channel) 来协调他们的工作 (e.g., 面对面交流, 书面笔记, 屏幕上的便签, 电话访谈, 电子邮件等). 共享锁的目的是让合作者知道还有谁可能正在处理该资源.
共享锁的引入是因为来自 Web 分布式编辑系统的经验表明, 互斥锁通常过于僵化. 互斥锁用于强制执行特定的编辑过程: 获取互斥锁, 读取资源, 执行编辑, 写入资源, 释放该锁. 这种编辑过程存在一个问题, 即锁不一定总被正确释放, 例如, 当程序崩溃或锁创建者离开而没有解锁资源时, 虽然超时 (第 6.6 章) 和管理员操作都可以用来移除有问题的锁, 但在有需要时两者可能都不可用; 超时可能很长, 而管理员也可能不在.
对于成功的请求而言, 新的共享锁必须[MUST]导致生成与请求主体关联的唯一锁. 因此, 如果有五个主体在同一资源上获取了共享写锁, 那么将有五个锁和五个锁令牌, 其中每个主体一个.
6.3. 必要性支持
WebDAV 兼容的资源不需要以任何形式支持锁. 如果资源支持锁, 其可以选择支持任何访问类型的互斥锁与共享锁之间的任何组合.
设计这种灵活性之处在于, 锁策略涉及到各种存储库所采用的资源管理和版本控制系统核心. 这些存储库需要控制自己能够提供哪种类型的锁. 例如, 一些存储库仅支持共享写锁, 而另一些仅支持互斥写锁, 而另一些则根本不使用锁. 由于每个系统的差异足以排除某些锁功能, 因此本规范将锁作为 WebDAV 内协商的唯一轴 (sole axis).
6.4. 锁创建者和权限
锁的创建者在使用锁来修改资源时具有特权. 当修改已锁定的资源时, 服务器必须[MUST]检查经过身份验证的主体是否与锁创建者匹配 (除了检查有效的锁令牌提交外).
服务器可以[MAY]允许除锁创建者之外的特权用户销毁锁 (e.g.,资源所有者或管理员). [RFC3744] 中定义的 "unlock" 特权提供了该权限.
对于服务器, 没有任何强制要求其接受来自所有用户或匿名用户的 LOCK 请求.
需要注意的是, 拥有锁并不会授予对已锁定资源进行全面修改的特权. 写入访问权限和其他特权必须[MUST]通过正常的特权或身份验证机制来强制执行并获取, 而不是基于锁令牌值中可能的模糊性.
6.5. 锁令牌
锁令牌是一种标识特定锁的状态令牌. 每把锁都有一个由服务器生成的唯一锁令牌. 客户端不得[MUST_NOT]尝试以任何方式自行解释锁令牌.
锁令牌URI 必须[MUST]在所有资源的所有时间内唯一.
此唯一性约束允许锁令牌在跨资源和服务器上提交而不用担心混淆.
由于锁令牌唯一, 客户端可以[MAY]在返回除自己外资源上的 If
标头中提交锁令牌.
当 LOCK 操作创建一个新锁时, 新的锁令牌会在 Lock-Token
响应标头中返回
(第 10.5 章中定义), 并且也会在响应正文中返回。
服务器可以[MAY]使锁定令牌公开可读 (e.g., 在 DAV:lockdiscovery
属性中).
一个使锁令牌可读取的用例是, 允许资源所有者删除被长期占有的锁 (获取锁的客户端可能在清理锁之前崩溃或断开连接).
除了在用户指导下使用 UNLOCK 的情况外, 客户端不应[SHOULD_NOT]使用由另一个客户端实例创建的锁令牌.
本规范鼓励服务器为锁令牌创建通用唯一标识符 (UUID), 并使用 "通用唯一标识符 (UUID) URN命名空间" ([RFC4122]) 中定义的 URI 形式. 但是, 服务器可以自由使用任何 URI (e.g., 来自另一个方案), 只要满足唯一性要求即可. 例如, 可以使用附录 C中定义的 "opaquelocktoken" 方案构造有效的锁令牌.
示例: "urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6"
6.6. 锁超时
一个锁可能[MAY]存在有限的生命周期. 客户端在创建或刷新锁时建议设置生命周期, 但服务器拥有选择设定超时值的最终决定权. 超时以该锁到期之前剩余的秒数为单位.
如果刷新锁请求成功 (见第 9.10.2 章), 则必须重新启动超时计数器. 超时计数器不应[SHOULD_NOT]在任何其他时候重启.
如果超时过期, 则锁应该[SHOULD]被移除, 这种情况下, 服务器应该[SHOULD]的行为表现应像在资源上使用 (超时锁的) 锁令牌执行了 UNLOCK方法, 并以其覆盖权限执行一样.
建议服务器密切关注客户端提交的值, 因为这些值将表明客户端打算执行的活动 (activity) 类型. 例如, 运行在浏览器中的小程序可能需要锁定资源, 但由于小程序运行环境的不稳定性, 其可能突然关闭. 因此,小程序可能会请求相对较小的超时值, 以便在其关闭时可以快速释放锁. 但是, 文档管理系统可能会请求极长的超时, 因为其用户可能计划脱机工作.
客户端不应[MUST_NOT]假设锁会仅因为超时已到期而被立即被移除.
同样的, 客户端不应[MUST_NOT]假设锁仅因为超时尚未到期而仍然存在. 客户端必须[MUST]假设锁可以在任何时候失效, 不论 Timeout标头中给定的值如何. Timeout标头仅指示服务器在不发生特殊情况时的行为. 例如, 具有足够特权的用户可以随时移除锁, 或是系统崩溃时候, 可能丢失锁存在的记录.
6.7. 锁能力发现
由于服务器对锁的支持是可选的, 因此尝试在服务器上锁定资源的客户端可以尝试锁定并期望一切顺利,
或者执行某种形式的发现以确定服务器所支持的锁功能. 这称为锁功能发现.
客户端可以通过检索 DAV:supportedlock
属性来确定服务器支持哪些锁类型.
任何支持 LOCK
方法的 DAV 兼容资源都必须[MUST]支持 DAV:supportedlock
属性.
6.8. 锁主动发现
如果另一个主体锁定了一个主体希望访问的资源, 那么第二个主体能够找出第一个主体是谁这一功能是很有用的.
为此 DAV:lockdiscovery
属性被提出. 该属性列出了所有未完成的锁, 并描述了它们的类型,
且甚至可能[MAY]提供了锁定令牌功能.