前言

继前两篇文章,介绍了两种搭建WebDav的方法:

  • Apache2后端,Nginx反向代理

  • Nginx直接搭建WebDav

这两种方法都存在一个很大的问题,Windows磁盘映射会出现各种错误,而且还不是单一的错误,这两天操碎了心,国内国外文章全部扒了一遍又一遍,所幸最终还是可以解决的。

问题:

  1. 无法创建、上传文件(夹)

  2. 提示:目录内已经存在相同的文件,是否覆盖

  3. 遗留一个0KB 的空白文件

  4. 另一个程序已经锁定文件的一部分,无法访问

问题的核心是 Windows 自带的 WebDAV 客户端(Microsoft-WebDAV-MiniRedir)与服务器的 WebDAV 模块之间的兼容性问题

这个问题不单纯是Nginx或者Apache2,其实很多第三方应用也是一样,比如我尝试过的Openlist,Hfs,它们可能一些小文件上传下载没问题,但是由于我经常要传输蓝光电影,文件大点也是一样的错误。

根源的Windows磁盘映射在搞事情,我们只能顺着它的心意。

顺带说一句,MacOS也不比它好,测试下来半斤八两。

重点

基于前两篇文章已经配置成功并能正常访问,我们只谈一下后续的步骤。

之前正常访问的配置文件:(抛弃Apache2,只用Nginx)

# /etc/nginx/site-enable/wedav.conf
server {
	
	listen 80;
	listen [::]:80;
	
	server_name _;
	
	location / {
		root /path/to/your/folder;
		dav_methods PUT DELETE MKCOL COPY MOVE;
		dav_ext_methods PROPFIND OPTIONS LOCK UNLOCK;
		dav_access group:rw all:r;
		autoindex on;
		auth_basic "WebDAV";
		auth_basic_user_file /etc/nginx/webdav.htpasswd;
		client_max_body_size 0;
		create_full_put_path on;
	}
	
}

此时,我们已经安装了 libnginx-mod-http-dav-ext 模块。

继续安装一个 http-headers-more-filter 模块:

apt install libnginx-mod-http-headers-more-filter

添加在 /etc/nginx/nginx.conf 的前端:

load_module modules/ngx_http_dav_ext_module.so; # 之前安装的
load_module modules/ngx_http_headers_more_filter_module.so; # 这次安装的

并且在 nginx.confhttp模块中添加一些配置参数:

http {
  dav_ext_lock_zone zone=foo:10m;  # 扩展锁定功能的内存区域
  client_max_body_size 0;
  create_full_put_path on;
  min_delete_depth 0;
  charset utf-8;
  source_charset utf-8;
  
  # 其他参数
  
}

然后编辑 /etc/nginx/site-enable/webdav.conf ,这部分仔细一些,一步步慢慢来。

# /etc/nginx/site-enable/wedav.conf
server {
	listen 80;
	listen [::]:80;
	
	server_name _;
	
	# 定义变量
	set $destination $http_destination;
	set $new_path "";
	set $webdav_root "/path/to/your/folder"; # 填写webdav路径
	set $checkPropfind "";
	
	#这个location块照抄就行,基本没有要改的东西
    location / {
		root $webdav_root;
		open_file_cache off;
		auth_basic "WebDAV";
		auth_basic_user_file /etc/nginx/webdav.htpasswd;
		
		dav_ext_lock zone=foo;
		dav_methods PUT DELETE MKCOL COPY MOVE;
		dav_ext_methods PROPFIND OPTIONS LOCK UNLOCK;
		
		autoindex on;
		autoindex_exact_size on;
		autoindex_localtime on;
		
		error_page		599 = @propfind_handler;
		error_page		598 = @delete_handler;
		error_page		597 = @copy_move_handler;
		error_page		596 = @propfind_withdepth_handler;
		
		if ($request_method != OPTIONS) {
			add_header 'Access-Control-Allow-Origin' '*' always;
			add_header 'Access-Control-Allow-Credentials' 'true' always;
			add_header 'Access-Control-Allow-Methods' 'OPTIONS, GET, HEAD, POST, PUT, MKCOL, MOVE, COPY, DELETE, PROPFIND, PROPPATCH, LOCK, UNLOCK' always;
			add_header 'Access-Control-Allow-Headers' 'Authorization,DNT,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,X-Accept-Charset,X-Accept,origin,accept,if-match,destination,overwrite' always;
			add_header 'Access-Control-Expose-Headers' 'ETag' always;
			add_header 'Access-Control-Max-Age' 1728000 always;
		}
		
		if ($request_method = OPTIONS) {
			add_header 'Content-Type' 'text/plain charset=UTF-8';
			add_header 'Access-Control-Allow-Origin' '*';
			add_header 'Access-Control-Allow-Credentials' 'true';
			add_header 'Access-Control-Allow-Methods' 'OPTIONS, GET, HEAD, POST, PUT, MKCOL, MOVE, COPY, DELETE, PROPFIND, PROPPATCH, LOCK, UNLOCK';
			add_header 'Access-Control-Allow-Headers' 'Authorization,DNT,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,X-Accept-Charset,X-Accept,origin,accept,if-match,destination,overwrite';
			add_header 'Access-Control-Expose-Headers' 'ETag';
			add_header 'Access-Control-Max-Age' 1728000;
			add_header	Allow 'OPTIONS, GET, HEAD, POST, PUT, MKCOL, MOVE, COPY, DELETE, PROPFIND, PROPPATCH, LOCK, UNLOCK';
			add_header	DAV '1, 2';
			return 200;
		}
		
		if ($request_method = PROPFIND) {
			set $checkPropfind "propfind";
		}
		
		if ($http_depth = 0) {
			set $checkPropfind "${checkPropfind}+withDepth";
		}
		
		if ($http_depth = 1) {
			set $checkPropfind "${checkPropfind}+withDepth";
		}
		
		if ($checkPropfind = "propfind") {
			return 599;
		}
		
		if ($checkPropfind = "propfind+withDepth") {
			return 596;
		}
		
		if ($request_method = PROPPATCH) {
			add_header	Content-Type 'text/xml';
			return		207 '<?xml version="1.0"?><a:multistatus xmlns:a="DAV:"><a:response><a:propstat><a:status>HTTP/1.1 200 OK</a:status></a:propstat></a:response></a:multistatus>';
		}
		
		if ($request_method = MKCOL) {
			rewrite ^(?<captured_path>.*[^/])$ $captured_path/ break;
		}
		if ($request_method = DELETE) {
			return 598;
		}
		if ($request_method = COPY) {
			return 597;
		}  
		if ($request_method = MOVE) {
			return 597;
		}
	} 
	
	# 这几个也不用修改
	location ~ \.(_.*|DS_Store|Spotlight-V100|TemporaryItems|Trashes|hidden|localized)$ {
		access_log off;
		error_log off;
		if ($request_method = PUT) {
			return 403;
		}
		return 404;
	}
	
	location ~ \.metadata_never_index$ {
		return 200 "Don't index this drive, Finder!";
	}
	
	location @propfind_handler {
		internal;
		open_file_cache	off;
		if (!-e $webdav_root/$uri) {
			return 404;
		}
		root			$webdav_root;
		dav_ext_methods		PROPFIND;
	}
	
	# 下面几个location把前两行的身份认证统一一下
	
	location @propfind_withdepth_handler {
		auth_basic "WebDAV";
		auth_basic_user_file /etc/nginx/webdav.htpasswd;
		internal;
		open_file_cache	off;
		
		if (!-e $webdav_root/$uri) {
			return 404;
		}
		root			$webdav_root;
		dav_ext_methods		PROPFIND;
	}
	
	location @delete_handler {
		auth_basic "WebDAV";
		auth_basic_user_file /etc/nginx/webdav.htpasswd;
		internal;
		open_file_cache	off;
		
		if ($destination ~ ^https?://(?<captured_path>.*)$) {
			set $new_path $captured_path;
			more_set_input_headers "Destination: http://$new_path";
		}   
		
		if (-d $webdav_root/$uri) {
			more_set_input_headers "Destination: http://$new_path/";
			rewrite ^(?<captured_path>.*[^/])$ $captured_path/ break;
		}    
		root			$webdav_root;
		dav_methods		DELETE;
	}
	
	location @copy_move_handler {
		auth_basic "WebDAV";
		auth_basic_user_file /etc/nginx/webdav.htpasswd;
		internal;
		open_file_cache	off;
		
		if ($destination ~ ^https?://(?<captured_path>.*)$) {
			set $new_path $captured_path;
			more_set_input_headers "Destination: http://$new_path";
		}
	    
		if (-d $webdav_root/$uri) {
			more_set_input_headers "Destination: http://$new_path/";
			rewrite ^(?<captured_path>.*[^/])$ $captured_path/ break;
		}    
		root			$webdav_root;
		dav_methods		COPY MOVE;
	}
	
}

重启Nginx后重新映射一下,这次应该没有问题~

结论

结论放前面,这个方法有点麻烦!不建议使用Windows自带的磁盘映射挂载WevDav!

必须要挂载的,不用上面的操作,直接参考网上的建议:使用第三方软件(如 RaiDrive, NetDrive, CarotDAV)!

不过有一点就是比如RaiDrive以前没有广告的,现在开始有广告了。只能自行抉择了。

后话

Windows其实对SMB支持非常好,但是SMB在外网简直无法直视。

WebDav呢?其实也有缺点,经过我的测试,在上传大文件(10G以上),在传输到99%时,会卡顿一会儿,大概率是因为上传文件是分块进行的,所有的块上传结束后再整合在一起。SMB就没有这个现象。

最后说说我个人的建议:

局域网环境: 大文件多的直接选择SMB,小文件多的两者都可;视频观看则两者都可;

外网环境: 文件传输两者都可;视频观看直接选择WebDav。

说人话就是,建议SMB和WebDav同时用。SMB负责局域网的文件传输,WebDav外网使用(因为外网受限于带宽,基本不会有很大的文件传输,小文件也能胜任,观看视频什么的更是它的强项)。

写这篇文章的时候头还是有点晕的,如果哪里有问题,欢迎各位领导指正,还是不行的话参考下面的文章,与自己的配置再核对核对。

文章中的配置我测试了一个早上,各个文件都试过,目前拷贝50G的文件也是没问题的。不排除我写文章的时候稀里糊涂的有遗漏的。

参考资料:

software:nginx:webdav [Net Lab]

docker-nginx-webdav-nononsense/nginx.conf at main · dgraziotin/docker-nginx-webdav-nononsense · GitHub