文章目录
- 什么是WebDav
- WebDav常用命令
- WebDav常用命令的测试(代码)
- PROPFIND 方法测试
- PUT 方法测试
- GET 方法测试
- PROPPATCH方法
- WebDav缓存
- Cache-Control
- Etag
- 测试
- 强制重新验证
- 不需要缓存
- WebDav的锁
- WebDav的状态码
- WebDav身份验证
- WebDav版本控制
- WebDav和FTP的区别
- 参考
什么是WebDav
What is WebDAV?
Briefly: WebDAV stands for “Web-based Distributed Authoring and Versioning”. It is a set of extensions to the HTTP protocol which allows users to collaboratively edit and manage files on remote web servers.
WebDAV Resources
WebDav是基于HTTP的协议,他可以允许客户端远程编辑Web内容。
WebDAV的特性和优势
支持创建、修改、复制、移动、移除、查询、列举文件
文件锁
版本控制
支持修改文件属性
安全完善的身份验证机制
支持https加密
支持proxy
客户端缓存
方便的客户端工具:和局域网中的文件共享一样简单使用。
来源:学习WebDav
WebDav常用命令
WebDav在HTTP的基础上扩展了自己的命令,例如:
PROPFIND 用于获取文件夹列表、文件夹内的文件列表、文件夹和文件的属性;
MKCOL 用于创建空文件夹;
PUT 用于上传文件;
GET 用于下载文件;
COPY 用于复制文件;
MOVE 用于移动文件;
WebDav常用命令的测试(代码)
我在坚果云网盘中,创建了几个文件夹,上传了几个文件。并按照如何在Zotero中设置webdav连接到坚果云?进行了网盘的WebDav服务配置,生成了WebDav密码。
根据学习WebDav ,直接在windows cmd使用curl命令就可以一定程度测试WebDav,我这里是在VS 2022中,通过libcurl库,向坚果云发送请求。
关于VS中如何导入libcurl库,可以看[libcurl] windows visual studio 导入libcurl库。
PROPFIND 方法测试
代码:
#include <curl/curl.h>
#include <iostream>
#include <fstream>using std::cout;
using std::endl;
using std::ios;#define ERROR(X) (cout << __FUNCDNAME__ << " " << (X) << " " << "error" << endl, -1)
#define ERROR2(X,Y) (cout << __FUNCDNAME__ << " " << (X) << " " << (Y) << " " << "error" << endl, -1)#if 1 // WebDav
size_t write_callback(char* ptr, size_t size, size_t nmemb, void* userdata);
int My_PROPFIND();
FILE* fp;int main()
{//打开一个文件,用于输出WebDav响应char filename[256];sprintf_s(filename, 256, "%s.%s", "WebDav-Test", "xml");errno_t err = fopen_s(&fp, filename, "wb");if (err)return ERROR2("fopen_s", err);//初始化curlcurl_global_init(CURL_GLOBAL_WIN32);//WebDav请求函数My_PROPFIND();curl_global_cleanup();cout << "program end." << endl;
}int My_PROPFIND()
{const char* host = "https://dav.jianguoyun.com";const char* url = "https://dav.jianguoyun.com/dav/box1";CURL* curl = curl_easy_init();if (curl) {//设置HTTP头 curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PROPFIND"); //修改HTTP方法curl_easy_setopt(curl, CURLOPT_URL, url); //设置URL curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, (long)CURL_HTTP_VERSION_1_1); //指定HTTP版本curl_easy_setopt(curl, CURLOPT_USERNAME, "这里隐藏掉邮箱地址@qq.com"); //设置访问WebDav账号和密码curl_easy_setopt(curl, CURLOPT_PASSWORD, "axs5pyhc2j6n7q");struct curl_slist* list = NULL; //设置HTTP头部字段list = curl_slist_append(list, "Connection: close"); //不要长连接list = curl_slist_append(list, "Accept: */*");curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);//指定用于SSL证书验证的证书CURLcode err = curl_easy_setopt(curl, CURLOPT_CAINFO, "D:\\SourceCode\\cert\\_.jianguoyun.com.crt");if (err != CURLE_OK) {cout << "CURLOPT_CAPATH err:" << err << endl;}//如果不设置,会出现:unable to get local issuer certificate的错误//设定HTTP响应的处理方法curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)fp);//设定控制台回显调试信息curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);//执行HTTP请求CURLcode ret = curl_easy_perform(curl);if (ret != CURLE_OK) {curl_easy_cleanup(curl);fclose(fp);return ERROR2("curl_easy_perform", ret);}}else {fclose(fp);return ERROR("curl_easy_init");}fclose(fp);curl_easy_cleanup(curl);return 0;
}size_t write_callback(char* ptr, size_t size, size_t nmemb, void* userdata)
{int realsize = size * nmemb;fwrite(ptr, 1, realsize, fp);return realsize;
}
#endif
控制台输出:
* Host dav.jianguoyun.com:443 was resolved.
* IPv6: (none)
* IPv4: 36.155.116.36, 36.155.116.35
* Trying 36.155.116.36:443...
* Connected to dav.jianguoyun.com (36.155.116.36) port 443
* ALPN: curl offers http/1.1
* CAfile: D:\SourceCode\cert\_.jianguoyun.com.crt
* CApath: none
* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256 / [blank] / UNDEF
* ALPN: server accepted http/1.1
* Server certificate:
* subject: CN=*.jianguoyun.com
* start date: Jan 23 00:00:00 2024 GMT
* expire date: Feb 19 23:59:59 2025 GMT
* subjectAltName: host "dav.jianguoyun.com" matched cert's "*.jianguoyun.com"
* issuer: C=GB; ST=Greater Manchester; L=Salford; O=Sectigo Limited; CN=Sectigo RSA Domain Validation Secure Server CA
* SSL certificate verify ok.
* Certificate level 0: Public key type ? (2048/112 Bits/secBits), signed using sha256WithRSAEncryption
* using HTTP/1.x
* Server auth using Basic with user ‘这里隐藏掉邮箱地址@qq.com'
> PROPFIND /dav/box1 HTTP/1.1
Host: dav.jianguoyun.com
Authorization: Basic MjgwMjAzNzEyN这里隐藏掉B5aGMyajZuN3F0eg==
Connection: close
Accept: */*< HTTP/1.1 207 Multi-Status
< Server: nginx
< Date: Mon, 19 Feb 2024 04:14:58 GMT
< Content-Type: text/xml; charset=UTF-8
< Content-Length: 2882
< Connection: close
< Pragma: no-cache
< Cache-Control: no-cache
<
* Closing connection
program end.
可以看到,发出的请求是:
> PROPFIND /dav/box1 HTTP/1.1
Host: dav.jianguoyun.com
Authorization: Basic MjgwMjAzNzEyN这里隐藏掉B5aGMyajZuN3F0eg==
Connection: close
Accept: */*
收到的响应HTTP头是:
< HTTP/1.1 207 Multi-Status
< Server: nginx
< Date: Mon, 19 Feb 2024 04:14:58 GMT
< Content-Type: text/xml; charset=UTF-8
< Content-Length: 2882
< Connection: close
< Pragma: no-cache
< Cache-Control: no-cache
输出到文件中的XML内容是:
<d:multistatus>
<d:response><d:href>/dav/box1/</d:href>
<d:propstat>
<d:prop><d:getcontenttype>httpd/unix-directory</d:getcontenttype><d:displayname>box1</d:displayname><d:owner>这里隐藏掉邮箱地址@qq.com</d:owner>
<d:resourcetype><d:collection/></d:resourcetype><d:getcontentlength>0</d:getcontentlength><d:getlastmodified>Mon, 19 Feb 2024 04:14:58 GMT</d:getlastmodified>
<d:current-user-privilege-set>
<d:privilege><d:read/></d:privilege>
<d:privilege><d:write/></d:privilege>
<d:privilege><d:all/></d:privilege>
<d:privilege><d:read_acl/></d:privilege>
<d:privilege><d:write_acl/></d:privilege></d:current-user-privilege-set></d:prop><d:status>HTTP/1.1 200 OK</d:status></d:propstat></d:response>
<d:response><d:href>/dav/box1/WeatherWS.xml</d:href>
<d:propstat>
<d:prop><d:getetag>UsZ7ybf73r39UXEEPQs5qA</d:getetag><d:getcontenttype>text/xml</d:getcontenttype><d:displayname>WeatherWS.xml</d:displayname><d:owner>这里隐藏掉邮箱地址@qq.com</d:owner><d:getcontentlength>29712</d:getcontentlength><d:getlastmodified>Fri, 29 Dec 2023 09:02:10 GMT</d:getlastmodified><d:resourcetype/>
<d:current-user-privilege-set>
<d:privilege><d:read/></d:privilege>
<d:privilege><d:write/></d:privilege>
<d:privilege><d:all/></d:privilege>
<d:privilege><d:read_acl/></d:privilege>
<d:privilege><d:write_acl/></d:privilege></d:current-user-privilege-set></d:prop><d:status>HTTP/1.1 200 OK</d:status></d:propstat></d:response>
<d:response><d:href>/dav/box1/box1_1</d:href>
<d:propstat>
<d:prop><d:getetag/><d:getcontenttype>httpd/unix-directory</d:getcontenttype><d:displayname>box1_1</d:displayname><d:owner>这里隐藏掉邮箱地址@qq.com</d:owner><d:getcontentlength>0</d:getcontentlength><d:getlastmodified>Tue, 13 Feb 2024 04:29:51 GMT</d:getlastmodified>
<d:resourcetype><d:collection/></d:resourcetype>
<d:current-user-privilege-set>
<d:privilege><d:read/></d:privilege>
<d:privilege><d:write/></d:privilege>
<d:privilege><d:all/></d:privilege>
<d:privilege><d:read_acl/></d:privilege>
<d:privilege><d:write_acl/></d:privilege></d:current-user-privilege-set></d:prop><d:status>HTTP/1.1 200 OK</d:status></d:propstat></d:response>
<d:response><d:href>/dav/box1/box1file.pdf</d:href>
<d:propstat>
<d:prop><d:getetag>rlLyz4SUXar-UNmip-F5Qw</d:getetag><d:getcontenttype>application/pdf</d:getcontenttype><d:displayname>box1file.pdf</d:displayname><d:owner>这里隐藏掉邮箱地址@qq.com</d:owner><d:getcontentlength>2422816</d:getcontentlength><d:getlastmodified>Wed, 15 Nov 2023 08:43:08 GMT</d:getlastmodified><d:resourcetype/>
<d:current-user-privilege-set>
<d:privilege><d:read/></d:privilege>
<d:privilege><d:write/></d:privilege>
<d:privilege><d:all/></d:privilege>
<d:privilege><d:read_acl/></d:privilege>
<d:privilege><d:write_acl/></d:privilege></d:current-user-privilege-set></d:prop><d:status>HTTP/1.1 200 OK</d:status></d:propstat></d:response></d:multistatus>
我对Dav中的box1文件夹发送了PROPDIND请求,在响应回来的XML内容中,列出了box1中的每个文件夹和文件(包括box1自己)。每个<d:response>节点都包含了一个文件夹或者文件,<d:response>节点,是文件夹或者文件的属性信息。
PUT 方法测试
只保留方法部分,其余代码省略。
int My_PUT()
{//这里需要指明需要在Dav上创建的文件的路径“box1”和名字“Upload_test.txt”const char* url = "https://dav.jianguoyun.com/dav/box1/Upload_test.txt";CURL* curl = curl_easy_init();if (curl) { //设置HTTP头 curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT"); //修改HTTP方法curl_easy_setopt(curl, CURLOPT_URL, url); //设置URL curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, (long)CURL_HTTP_VERSION_1_1); //指定HTTP版本curl_easy_setopt(curl, CURLOPT_USERNAME, "2xxxxxxxx4@qq.com"); //设置访问WebDav账号和密码curl_easy_setopt(curl, CURLOPT_PASSWORD, "axs5pyhc2j6n7q");struct curl_slist* list = NULL; //设置HTTP头部字段list = curl_slist_append(list, "Connection: close"); //不要长连接list = curl_slist_append(list, "Accept: */*");curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);//设置要上传的文件信息//打开文件curl_off_t fsize = 0;FILE* src = nullptr;errno_t ferr = fopen_s(&src, "D:\\SourceCode\\TransFILE1.txt", "rb");if (ferr)return -1;//获取文件大小fseek(src, 0, SEEK_END); fsize = ftell(src);fseek(src, 0, SEEK_SET);curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_cb); //设置读取文件的回调函数curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L); //启动Upload服务curl_easy_setopt(curl, CURLOPT_READDATA, src);//设置传入回调函数的文件句柄curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)fsize);//设置文件大小//指定用于SSL证书验证的证书CURLcode err = curl_easy_setopt(curl, CURLOPT_CAINFO, "D:\\SourceCode\\cert\\_.jianguoyun.com.crt");if (err != CURLE_OK) {cout << "CURLOPT_CAPATH err:" << err << endl;}//如果不设置,会出现:unable to get local issuer certificate的错误//设定HTTP响应的处理方法curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)fp);//设定控制台回显调试信息curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);//执行HTTP请求CURLcode ret = curl_easy_perform(curl);if (ret != CURLE_OK) {curl_easy_cleanup(curl);fclose(fp);return ERROR2("curl_easy_perform", ret);}}else {fclose(fp);return ERROR("curl_easy_init");}fclose(fp);curl_easy_cleanup(curl);return 0;
}
static size_t read_cb(char* ptr, size_t size, size_t nmemb, void* userdata)
{FILE* src = (FILE*)userdata;/* copy as much data as possible into the 'ptr' buffer, but no more than'size' * 'nmemb' bytes */size_t retcode = fread(ptr, size, nmemb, src);return retcode;
}
控制台回显信息(部分):
> PUT /dav/box1/Upload_test.txt HTTP/1.1
Host: dav.jianguoyun.com
Authorization: Basic MjgwMjAzNzEyNEB---------------aGMyajZuN3F0eg==
Connection: close
Accept: */*
Content-Length: 1844* We are completely uploaded and fine
< HTTP/1.1 204 No Content
< Server: nginx
< Date: Mon, 19 Feb 2024 05:59:17 GMT
< Connection: close
< X-File-Version: 3
< Pragma: no-cache
< Cache-Control: no-cache
<
* Closing connection
WebDav查看:
GET 方法测试
int My_GET()
{const char* url = "https://dav.jianguoyun.com/dav/box1/Upload_test.txt";CURL* curl = curl_easy_init();if (curl) {//设置HTTP头 curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "GET"); //修改HTTP方法curl_easy_setopt(curl, CURLOPT_URL, url); //设置URL curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, (long)CURL_HTTP_VERSION_1_1); //指定HTTP版本curl_easy_setopt(curl, CURLOPT_USERNAME, "2--------4@qq.com"); //设置访问WebDav账号和密码curl_easy_setopt(curl, CURLOPT_PASSWORD, "axs5pyhc2j6n7q");struct curl_slist* list = NULL; //设置HTTP头部字段list = curl_slist_append(list, "Connection: close"); //不要长连接list = curl_slist_append(list, "Accept: */*");curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);//指定用于SSL证书验证的证书CURLcode err = curl_easy_setopt(curl, CURLOPT_CAINFO, "D:\\SourceCode\\cert\\_.jianguoyun.com.crt");if (err != CURLE_OK) {cout << "CURLOPT_CAPATH err:" << err << endl;}//如果不设置,会出现:unable to get local issuer certificate的错误//设定HTTP响应的处理方法curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)fp);//设定控制台回显调试信息curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);//执行HTTP请求CURLcode ret = curl_easy_perform(curl);if (ret != CURLE_OK) {curl_easy_cleanup(curl);fclose(fp);return ERROR2("curl_easy_perform", ret);}}else {fclose(fp);return ERROR("curl_easy_init");}fclose(fp);curl_easy_cleanup(curl);return 0;
}
控制台回显结果(部分):
> GET /dav/box1/Upload_test.txt HTTP/1.1
Host: dav.jianguoyun.com
Authorization: Basic MjgwMjAzNzEyNEBxc--------------GMyajZuN3F0eg==
Connection: close
Accept: */*< HTTP/1.1 200 OK
< Server: nginx
< Date: Mon, 19 Feb 2024 06:13:18 GMT
< Content-Type: text/plain
< Content-Length: 1844
< Connection: close
< Etag: 8sKBsnMc5tH71U67xjQTCQ
< Pragma: public
< Cache-Control: max-age=5
< Content-Disposition: attachment
<
* Closing connection
program end.
下载的文件内容保存在以下代码绑定的文件中了:
//设定HTTP响应的处理方法curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)fp);
PROPPATCH方法
PROPPATCH方法用于修改文件的属性。
WebDav方法的HTTP Body是XML格式,前面尝试的几个请求都没有添加Body。
PROPPATCH需要在Body中添加需要修改的属性指令。
以刚才PUT的文件Upload_test.txt为目标,把它的<d:displayname>修改为Upload_test_1.txt。
但是没有效果,坚果云给的响应中消息中,也没有显示失败信息。
我咨询了坚果云的客服,客服联系技术给出了回复,目前坚果云不支持PROPPATCH方法
因此,无法验证我的代码是否正确,但是还是记录一下代码,期待以后有机会验证。
代码:
int My_PROPPATCH()
{const char* url = "https://dav.jianguoyun.com/dav/box1/Upload_test.txt";CURL* curl = curl_easy_init();if (curl) {//设置HTTP头 curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PROPPATCH"); //修改HTTP方法curl_easy_setopt(curl, CURLOPT_URL, url); //设置URL curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, (long)CURL_HTTP_VERSION_1_1); //指定HTTP版本curl_easy_setopt(curl, CURLOPT_USERNAME, "2802037124@qq.com"); //设置访问WebDav账号和密码curl_easy_setopt(curl, CURLOPT_PASSWORD, "axs5pyhc2j6n7qtz");struct curl_slist* list = NULL; //设置HTTP头部字段//list = curl_slist_append(list, "Connection: close"); //不要长连接list = curl_slist_append(list, "Accept: */*");list = curl_slist_append(list, "Content-Type:application/xml; charset= 'utf-8'");curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);//指定用于SSL证书验证的证书CURLcode err = curl_easy_setopt(curl, CURLOPT_CAINFO, "D:\\SourceCode\\cert\\_.jianguoyun.com.crt");if (err != CURLE_OK) {cout << "CURLOPT_CAPATH err:" << err << endl;}//如果不设置,会出现:unable to get local issuer certificate的错误//设定HTTP响应的处理方法curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)fp);//设定控制台回显调试信息curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);//使用TinyXML库,添加Http BodyTiXmlDocument* tinyXmlDoc = new TiXmlDocument();TiXmlDeclaration* tinyXmlDeclare = new TiXmlDeclaration("1.0", "utf-8", ""); // xml的声明tinyXmlDoc->LinkEndChild(tinyXmlDeclare);TiXmlElement* Library = new TiXmlElement("D:propertyupdate");Library->SetAttribute(" xmlns:D", "DAV");Library->SetAttribute(" xmlns:S", "http://ns.jianguoyun.com");tinyXmlDoc->LinkEndChild(Library); TiXmlElement* Set = new TiXmlElement("D:set");Library->LinkEndChild(Set);TiXmlElement* Prop = new TiXmlElement("D:prop");Set->LinkEndChild(Prop);TiXmlElement* Displayname2 = new TiXmlElement("S:publish");TiXmlText* newname = new TiXmlText("Upload_test_1.txt"); Displayname2->LinkEndChild(newname); Prop->LinkEndChild(Displayname2);TiXmlPrinter printer;tinyXmlDoc->Accept(&printer);printf("%s\n", printer.CStr());char body[1024] = { 0x00 };strcpy_s(body, (rsize_t)1024, printer.CStr());curl_off_t size = strlen(body);curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_cb_patch); //设置读取文件的回调函数curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L); //启动Upload服务curl_easy_setopt(curl, CURLOPT_READDATA, body);//设置传入回调函数的文件句柄curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)size);//设置文件大小//执行HTTP请求CURLcode ret = curl_easy_perform(curl);if (ret != CURLE_OK) {curl_easy_cleanup(curl);/*fclose(fp);*/return ERROR2("curl_easy_perform", ret);}}else {//fclose(fp);return ERROR("curl_easy_init");}//fclose(fp);curl_easy_cleanup(curl);return 0;
}
控制台回显:
> PROPPATCH /dav/box1/Upload_test.txt HTTP/1.1
Host: dav.jianguoyun.com
Authorization: Basic MjgwMjAzNzEyNEBxcS5jb206YXhzNXB5aGMyajZuN3F0eg==
Accept: */*
Content-Type:application/xml; charset= 'utf-8'
Content-Length: 243* We are completely uploaded and fine
< HTTP/1.1 207 Multi-Status
< Server: nginx
< Date: Mon, 19 Feb 2024 09:23:56 GMT
< Content-Type: text/xml; charset=UTF-8
< Content-Length: 524
< Connection: keep-alive
< Keep-Alive: timeout=60
< Pragma: no-cache
< Cache-Control: no-cache
Response的正文:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<d:multistatus xmlns:d="DAV:" xmlns:s="http://ns.jianguoyun.com">
<d:response>
<d:href>/dav/box1/Upload_test.txt</d:href>
<d:propstat>
<d:prop>
<m:Win32LastModifiedTime xmlns:m="urn:schemas-microsoft-com:"/>
<m:Win32FileAttributes xmlns:m="urn:schemas-microsoft-com:"/>
<m:Win32CreationTime xmlns:m="urn:schemas-microsoft-com:"/>
<m:Win32LastAccessTime xmlns:m="urn:schemas-microsoft-com:"/>
</d:prop><d:status>HTTP/1.1 200 OK</d:status>
</d:propstat></d:response></d:multistatus>
WebDav缓存
在上面的PROPFIND等请求的响应头中,能看到以下字段:
< Etag: 8sKBsnMc5tH71U67xjQTCQ
< Cache-Control: max-age=5
Cache-Control
Cache-Control: max-age=5 就是控制缓存的过期时间,这里是5秒后缓存过期。
Cache-Control也可以用来设置缓存类型,Cache-Control: private //私有缓存
Cache-Control: public //贡献缓存。
Etag
他们是用于HTTP缓存控制的字段。
Etag响应头,是HTTP中资源的特定版本标识符。
Etag相当于资源的指纹, URL 中的资源更改了,就一定要生成新的 ETag 值。
Etag由服务器生成,在客户端请求资源时通过Etag响应头发给客户端。
客户端下次请求同一个资源时,如果资源已经过期,客户端请求通过If-None-Match请求头,把Etag的值发给服务器,服务器可以通过If-None-Match的值,判断资源是否已经改变(这个过程叫做重新验证)。如果客户端的If-None-Match和服务器资源当前的Etag一致,服务器就不需要发送完整数据了,返回一个 304 Not Modified 状态即可。
测试
首先我用PROPFIND获取了WebDav中,一个文件的属性,它的Etag是:
<d:getetag>uLR1Dl0O-2f8uVxiMCSTGQ</d:getetag>
我在GET方法的请求头中,添加了If-None_Match字段,值就是刚才获取的文件Etag。
list = curl_slist_append(list, "If-None-Match: uLR1Dl0O-2f8uVxiMCSTGQ");
发送GET方法请求后,服务器返回304 Not Modified,即文件没有变动,无需重新获取数据。
强制重新验证
如果服务器想要客户端在资源没有过期的时候,也要获取最新的资源。
可以在 存在Etag或者Last-Modified头的同时,指定Cache-Control: no-cache或Cache-Control: max-age=0, must-revalidate
不需要缓存
指定Cache-Control: no-cache
GET、HEAD、OPTIONS 方法是幂等的,不会改变服务器资源的状态,是可以缓存的。
其余的方法是不建议缓存,或者不可以缓存的。
完整的缓存控制可以参考:【HTTP完全注解】看了还搞不懂缓存你直接来打我
WebDav的锁
WebDav 规范中存在排他锁、共享锁。
WebDav规范中只规定了写入锁(Write),不同的服务器可能实现了不同类型的锁。
在不同的服务器中,可能不支持锁,或者支持一种锁,或者支持多种锁。
WebDav中的每一个锁都会生成一个锁令牌(lock token),在对被锁住的对象进行操作室,HTTP头必须提交锁令牌信息。
有LOCK和UNLOCK方法来进行枷锁和解锁。
WebDav的状态码
WebDav扩展了以下状态码:
207:多状态,查看响应正文来获取详细状态。
422:请求URL存在,但是请求正文的XML内容不正确
423:请求的文件对象已被锁定
424:依赖失败,比如PROPPATCH中的一个属性修改命令失败,其余的命令也会失败。
507:服务器暂时无法提供存储空间
WebDav身份验证
通过TLS确保Basic验证信息安全。
WebDav版本控制
TODO.
参考:Versioning Extensions to WebDAV
WebDav和FTP的区别
WebDav提供了缓存功能,FTP没有;
WebDav一般通过HTTPS的443端口通信,FTP需要用20和21端口通信。
WebDav提供了锁,FTP没有。
参考
WebDAV Resources
WebDAV 规范文档
WebDAV 规范文档-Gitee
学习WebDav
如何在Zotero中设置webdav连接到坚果云?
【HTTP完全注解】看了还搞不懂缓存你直接来打我
http 三种认证方式 Basic Session Token 简介
Versioning Extensions to WebDAV