📘 第九章 基于HTTP的功能追加协议
📄 一句话概括

WebDAV 是给 HTTP 加的一组方法和状态码,目的是让 Web 服务器能当网盘用:列目录、建目录、复制、 改名、加锁。它的招牌是 207 Multi-Status——一个请求回来一批对象的状态。

📝 本节和前面几节的关系

前面三个协议都在改「传输」。WebDAV 是最朴素的一条路:协议不动,只给它添几个动词。 这一节看它添了什么、怎么回话。

一、把服务器当硬盘用

HTTP 本来就能 GET 和 PUT,理论上已经能读写文件了。 但真当网盘用的时候,你会发现缺一堆动作:怎么列目录?怎么建目录?怎么复制一个文件? 怎么防止两个人同时改同一份? WebDAV 就是补这些的。它在 1999 年成为 RFC 2518,后来修订成 RFC 4918,一直用到现在。 现实里你可能天天在用而不自知:Windows 的「映射网络驱动器」、macOS 访达里的「连接服务器」、 Office 的共享编辑,底层都可能是 WebDAV。它不显眼,但活得比 SPDY 久多了。

二、加了七个方法

PROPFIND 读属性(列目录就是读一批属性)、PROPPATCH 写属性、MKCOL 建集合(相当于建目录)、 COPY 复制、MOVE 改名或挪位置、LOCK 加锁、UNLOCK 解锁。 加上原有的 OPTIONS、GET、HEAD、PUT、DELETE,一共 12 个方法。 Demo 里 OPTIONS 回的 Allow 首部把这 12 个全列了出来。 注意 OPTIONS 的响应里还有一行 DAV: 1, 2。 这个首部是 WebDAV 的身份证:1 表示支持基本功能,2 表示支持锁定。看到它,客户端才敢往下走。

三、207 是它的招牌

普通 HTTP 一次请求只有一个结果,所以一个状态码就够。 但 PROPFIND 一次要回一个目录下所有文件的属性——每个文件都可能成功或失败。 所以 WebDAV 造了一个新码:207 Multi-Status。 状态行写 207,正文是一段 XML,里面一个 <D:response> 一条结果,每条自带自己的 <D:status>。 Demo 里 Depth: 0 只要自己那一条,回 400 字节、1 个 response; Depth: 1 要自己和直接子项,回 1018 字节、3 个 response。 一个请求装了三份结果,这在 HTTP/1.1 里是没有的表达方式。 注意每个 <D:response> 里用 href 指对象——那是路径,不是标题。 客户端拿到就是拿路径去对,所以路径里的编码不能随便动。

四、动手 Demo:OPTIONS 问能力,PROPFIND 要清单

一个本机 WebDAV 服务端,内存里存几个资源。 先 OPTIONS 问它支持什么,再建一个集合、放两个文件, 然后用 Depth: 0 和 Depth: 1 各 PROPFIND 一次,把 207 的正文原样打出来。

def prop_xml(path, item):
    body, kind, mt = item
    rt = u'<D:collection/>' if kind == u'col' else u''
    return (u'<D:response><D:href>%s</D:href><D:propstat><D:prop>'
            u'<D:displayname>%s</D:displayname>'
            u'<D:getcontentlength>%d</D:getcontentlength>'
            u'<D:getlastmodified>%s</D:getlastmodified>'
            u'<D:resourcetype>%s</D:resourcetype>'
            u'</D:prop><D:status>HTTP/1.1 200 OK</D:status>'
            u'</D:propstat></D:response>' % (path, name, len(body), mt, rt))

实跑输出:

--------------------------------------------------------------------
OPTIONS 与 PROPFIND:207 里装的是什么
--------------------------------------------------------------------
  OPTIONS 状态行             HTTP/1.1 200 OK
  DAV 首部                   1, 2
  Allow 首部长度             85 字节

  Allow 列出的方法           共 12 个
  这 12 个方法里,只有前 3 个是 HTTP/1.1 本来有的(OPTIONS、GET、HEAD),
  剩下 9 个全是 WebDAV 加上去的。DAV: 1, 2 里那个 2 表示支持锁定。

  Depth: 0 的状态行          HTTP/1.1 207 Multi-Status
  Depth: 0 的 Content-Type   application/xml; charset="utf-8"
  Depth: 0 正文              400 字节,1 个 response
  Depth: 1 的状态行          HTTP/1.1 207 Multi-Status
  Depth: 1 正文              1018 字节,3 个 response

  Depth: 1 回来的原文(换行只是为了好看):
    <?xml version="1.0" encoding="utf-8"?>
    <D:multistatus xmlns:D="DAV:">
    <D:response>
    <D:href>/dav</D:href>
    <D:propstat>
    <D:prop>
    <D:displayname>dav</D:displayname>
    <D:getcontentlength>0</D:getcontentlength>
    <D:getlastmodified>Sun, 25 Oct 2026 07:03:59 GMT</D:getlastmodified>
    <D:resourcetype>
    <D:collection/>
    </D:resourcetype>
    </D:prop>
    <D:status>HTTP/1.1 200 OK</D:status>
    </D:propstat>
    </D:response>
    <D:response>
    <D:href>/dav/a.txt</D:href>
    <D:propstat>
    <D:prop>
    <D:displayname>a.txt</D:displayname>
    <D:getcontentlength>5</D:getcontentlength>
    <D:getlastmodified>Sun, 25 Oct 2026 07:03:59 GMT</D:getlastmodified>
    <D:resourcetype>
    </D:resourcetype>
    </D:prop>
    <D:status>HTTP/1.1 200 OK</D:status>
    </D:propstat>
    </D:response>
    <D:response>
    <D:href>/dav/b.txt</D:href>
    <D:propstat>
    <D:prop>
    <D:displayname>b.txt</D:displayname>
    <D:getcontentlength>7</D:getcontentlength>
    <D:getlastmodified>Sun, 25 Oct 2026 07:03:59 GMT</D:getlastmodified>
    <D:resourcetype>
    </D:resourcetype>
    </D:prop>
    <D:status>HTTP/1.1 200 OK</D:status>
    </D:propstat>
    </D:response>
    </D:multistatus>

  整个 XML 里 <D: 出现       29 次
  注意两点:状态码是 207,不是 200;正文是 XML,一个响应里塞了多条结果。
  这就是 WebDAV 的招牌做法——一次请求回一批对象的状态,
  每个对象自带一条 status,所以整批里可以有的成功有的失败。

OPTIONS 回 200,带 DAV: 1, 2,Allow 首部 85 字节、列了 12 个方法。 这一问一答就是客户端「探能力」的标准动作。 PROPFIND 两次都回 207 Multi-Status,Content-Type 是 application/xml; charset="utf-8"。 状态码不是 200——这一点最容易记错。 Depth: 0 回 400 字节 / 1 个 response,Depth: 1 回 1018 字节 / 3 个 response。 Depth 就是「要几层」,它是个请求首部,不是路径参数。 XML 里 <D: 出现 29 次。 看着啰嗦,但它换来的是「一个响应里装 N 条结果,每条自带状态」——这正是 WebDAV 存在的理由。

30 秒实验

  1. 把 Depth: 1 改成 Depth: infinity,看服务端该怎么处理(很多实现直接拒绝)。
  2. 给 PROPFIND 的请求体里只点名要 getcontentlength,看返回会不会瘦下来。
  3. 想一想:为什么 PROPFIND 不用 GET?用 GET 能不能表达「列一批属性」?

常见误区

  1. 「WebDAV 是一个新协议」——它是 HTTP 的扩展,方法和状态码都是加在 HTTP 上的。
  2. 「207 等于成功」——207 只表示「一批结果回来了」,每条结果要自己看 status。
  3. 「PROPFIND 就是 GET 目录」——GET 一个目录在多数服务器上是 403 或 404。
自测三问(点开对答案) 1. WebDAV 一共给 HTTP 加了几个方法?分别是什么? —— 七个:PROPFIND、PROPPATCH、MKCOL、COPY、MOVE、LOCK、UNLOCK。 2. 207 这个状态码是干什么的? —— 一次返回一批对象的状态,正文是 XML,每条结果自带 status。 3. `DAV: 1, 2` 里的 2 表示什么? —— 支持锁定(LOCK/UNLOCK)。1 表示支持基本功能。

方法有了,回话的码也得有。下一节把这些方法挨个跑一遍,看各回什么。

相关笔记