В Microsoft Windows начиная с 2к встроен Веб Клиент, по сути это средство для монтирования сетевых дисков по протоколу WebDav, который ходит поверх HTTP/HTTPS.
Клиент писался явно под себя (связка с IIS) и от того работает весьма корявосвоеобразно.
Это единственный способ «малой» кровью примонтировать диск через интернет не настраивая VPN для проброса SAMBA протокола.
SAMB-у в принципе тоже без VPN можно пробросить, но это из области поиска приключений с последствиями.
Nginx в базовом функционале имеет не полную поддержку WebDav: PUT DELETE MKCOL COPY MOVE.
Расширить его ещё двумя: PROPFIND и OPTIONS можно с помощью плагина: dav-ext
Дальше будет описание как заставить виндовый WebDav клиент работать с Nginx колдуя только в конфиге.
+ пара мелких багов Nginx.
Для работы потребуется Nginx собранный с поддержкой WebDav, dav-ext модулем и rewrite.
[x] HTTP_DAV Enable http_webdav module
[x] HTTP_DAV_EXT 3rd party webdav_ext module
[x] HTTP_REWRITE Enable http_rewrite module
Проблема 1
Майкрософт нарушает стандарты и свои обещания. (как обычно)
Перед тем как создать файл WebDav клиент проверяет наличие файла посылая запрос:
PROPFIND /!!!!/test.lnk HTTP/1.1 Connection: Keep-Alive User-Agent: Microsoft-WebDAV-MiniRedir/6.1.7601 Depth: 0 translate: f Content-Length: 0 Host: 172.16.0.254:8089
HTTP/1.1 404 Resource Not Found Content-Length: 1635 Content-Type: text/html Server: Microsoft-IIS/6.0 X-Powered-By: ASP.NET Date: Sun, 10 Aug 2014 20:06:08 GMT <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> ...
Только это не правильно, должен быть код 207 и xml в котором описан элемент и код для него, те 404 должно быть в xml.
с точки зрения стандартов: tools.ietf.org/html/rfc2518#page-24
с точки зрения доков мс: msdn.microsoft.com/en-us/library/aa142960(v=exchg.65).aspx
У яндекса тоже в примерах xml: api.yandex.ru/disk/doc/dg/reference/propfind_property-request.xml
HTTP/1.1 207 Multi-Status Server: nginx/1.7.4 Date: Sun, 10 Aug 2014 21:17:34 GMT Transfer-Encoding: chunked Connection: keep-alive Keep-Alive: timeout=60 47 <?xml version="1.0" encoding="utf-8" ?> <D:multistatus xmlns:D="DAV:"> cc <D:response> <D:href>/Family/INSURGENCIES AND COUNTERING INSURGENCIES.pdf - ...........lnk</D:href> <D:propstat> <D:prop> </D:prop> <D:status>HTTP/1.1 404 Not Found</D:status> </D:propstat> </D:response> 11 </D:multistatus> 0
Такой «неожиданный» ответ сносит голову WebDav клиенту винды.
Фиксим в конфиге (возможно это сведёт с ума остальные, порядочные WebDav клиенты, но лично мне нужен был только один не такой как все):
error_page 599 = @propfind_handler; if ($request_method = PROPFIND) { return 599; } location @propfind_handler { internal; open_file_cache off; if (!-e $webdav_root/$uri) { # Microsoft specific handle. return 404; } root $webdav_root; dav_ext_methods PROPFIND; }
Проблема 2
WebDav от мс очень хочет метод PROPPATCH, которого в Nginx и расширениях нет. Совсем нет.
Я рассматривал два варианта решения:
1. Написать плагин к Nginx или патч к dav-ext, короче Си код и пересборка Nginx.
2. Положится на кривость виндовой реализации WebDav и скормить статический ответ.
PROPPATCH /Family/INSURGENCIES%20AND%20COUNTERING%20INSURGENCIES.pdf%20-%20%D0%AF%D1%80%D0%BB%D1%8B%D0%BA.lnk HTTP/1.1 Cache-Control: no-cache Connection: Keep-Alive Pragma: no-cache Content-Type: text/xml; charset="utf-8" User-Agent: Microsoft-WebDAV-MiniRedir/6.1.7601 translate: f Content-Length: 443 Host: xxx.xxx.net <?xml version="1.0" encoding="utf-8" ?><D:propertyupdate xmlns:D="DAV:" xmlns:Z="urn:schemas-microsoft-com:"><D:set><D:prop><Z:Win32CreationTime>Sun, 10 Aug 2014 21:30:21 GMT</Z:Win32CreationTime><Z:Win32LastAccessTime>Sun, 10 Aug 2014 21:30:22 GMT</Z:Win32LastAccessTime><Z:Win32LastModifiedTime>Sun, 10 Aug 2014 21:30:22 GMT</Z:Win32LastModifiedTime><Z:Win32FileAttributes>00000020</Z:Win32FileAttributes></D:prop></D:set></D:propertyupdate>
HTTP/1.1 207 Multi-Status Date: Sun, 10 Aug 2014 12:24:47 GMT Server: Microsoft-IIS/6.0 X-Powered-By: ASP.NET Content-Type: text/xml Transfer-Encoding: chunked <?xml version="1.0"?><a:multistatus xmlns:a="DAV:" xmlns:b="urn:schemas-microsoft-com:"><a:response><a:href>http://172.16.0.254:8088//!!!!/INSURGENCIES%20AND%20COUNTERING%20INSURGENCIES.pdf%20-%20%D0%AF%D1%80%D0%BB%D1%8B%D0%BA.lnk</a:href><a:propstat><a:status>HTTP/1.1 200 OK</a:status><a:prop><b:Win32CreationTime/><b:Win32LastAccessTime/><b:Win32LastModifiedTime/><b:Win32FileAttributes/></a:prop></a:propstat></a:response></a:multistatus> 0
«Пропритериарщина», — подумал я. Вот кому такое вообще надо?
И как эти все атрибуты писать в разных ОС и разных ФС?..
Так появился второй фикс в конфиге:
if ($request_method = PROPPATCH) { # Unsupported, allways return OK. 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>'; }
Из ответа IIS я выкинул всё что мне не понравилось и виндовый WebDav это проглотил.
Теперь одиночные и группы файлов без проблем можно копировать на сетевой диск примонтированный по WebDav.
Минусом — не выставляется оригинальная дата создания, модификации и атрибуты. Я переживу.
Проблема 3
Создание папок.
См п1 :)
MKCOL /Family/MR3020 HTTP/1.1 Connection: Keep-Alive User-Agent: Microsoft-WebDAV-MiniRedir/6.1.7601 translate: f Content-Length: 0 Host: xxx.xxx.net
Nginx на него отвечает (немного странно выбирая код, на мой взгляд, но в принципе правильно)
HTTP/1.1 409 Conflict Server: nginx/1.7.4 Date: Sun, 10 Aug 2014 21:43:15 GMT Content-Type: text/html Content-Length: 166 Connection: keep-alive Keep-Alive: timeout=60 <html> <head><title>409 Conflict</title></head> <body bgcolor="white"> <center><h1>409 Conflict</h1></center> <hr><center>nginx/1.7.4</center> </body> </html>
IIS отвечает: HTTP/1.1 201 Created, смотреть там не на что.
Как должно быть описано:
У майкрософта: msdn.microsoft.com/en-us/library/aa142923(v=exchg.65).aspx
У яндекса: api.yandex.ru/disk/doc/dg/reference/mkcol.xml
И даже в RFC: tools.ietf.org/html/rfc2518#page-33
Во всех примерах URL оканчивается слешем.
Но только не в запросе WebDav клиента от мс.
Вариантов опять было два:
1. Поправить файл: lxr.nginx.org/source/src/http/modules/ngx_http_dav_module.c строчки 484 — 493, там как раз проверка наличия слеша и его отрезание.
2. Пофиксить через конфиг.
Вариант 1 я оставляю на усмотрение програмеров nginx, может по стандарту оно и должно ругаться. Связываться с отсылкой патчей тоже не хотелось.
Правим конфиг:
if ($request_method = MKCOL) { # Microsoft specific handle: add trailing slash. rewrite ^(.*[^/])$ $1/; }
Вот для этого пустяка и потребовался REWRITE плагин.
Проблема 4
Удаление папок.
Ноги тут те же что и в п3: отсутствие слеша на конце урла.
Однако я столкнулся с тем, что nginx тоже ведёт себя несколько странно.
1. Если слеш на конце отсутствует, то nginx считает что его просят удалить файл, и получает ошибку: 21: Is a directory при попытке удалить.
2. Если слеш добавить то папку он почему то так и не удаляет. Подозреваю тут где то баг самого nginx.
Фикс в конфиге:
error_page 598 = @delete_handler; if ($request_method = DELETE) { return 598; } location @delete_handler { internal; open_file_cache off; if (-d $webdav_root/$uri) { # Add trailing slash to dirs. rewrite ^(.*[^/])$ $1/; } root $webdav_root; dav_methods DELETE; }
Если кратко, то переносим обработку DELETE в отдельный локейшин (процедуру), дальше проверяем, если удаляется папка то добавляем слеш.
Ещё немного о Nginx
Не трудно заметить, что nginx не добавляет в ответы:
Content-Type: text/xml
и хотя в данном случае это не создаёт проблем, но всё же это не правильно.
Это уже третий раз когда гибкость nginx позволила мне получить результат не используя языки программирования, Игорь — молодец!
Прошлые разы я сделал кешируюший прокси с фильтрацией по урл и UPnP/DLNA сервер.
Конфиг nginx для WebDav
# WebDAV set $webdav_root "/mnt/WebDav_folder"; location ^~ /Family { root $webdav_root; error_page 599 = @propfind_handler; error_page 598 = @delete_handler; chunked_transfer_encoding on; open_file_cache off; client_max_body_size 50m; add_header Allow 'OPTIONS, GET, HEAD, DELETE, PUT, COPY, MOVE, PROPFIND'; if ($request_method = PROPFIND) { return 599; } if ($request_method = PROPPATCH) { # Unsupported, allways return OK. 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) { # Microsoft specific handle: add trailing slash. rewrite ^(.*[^/])$ $1/; } if ($request_method = DELETE) { return 598; } dav_methods PUT MKCOL COPY MOVE; # dav_ext_methods OPTIONS; create_full_put_path on; min_delete_depth 0; dav_access user:rw group:rw all:rw; } location @propfind_handler { internal; open_file_cache off; if (!-e $webdav_root/$uri) { # Microsoft specific handle. return 404; } root $webdav_root; dav_ext_methods PROPFIND; } location @delete_handler { internal; open_file_cache off; if (-d $webdav_root/$uri) { # Add trailing slash to dirs. rewrite ^(.*[^/])$ $1/; } root $webdav_root; dav_methods DELETE; }
open_file_cache off; — нужно потому что мы меняем файлы, если кеширование включено то можно будет долго гадать почему не видно файлы которые мы только что закинули на сервер.
Немного о настройке клиента
Он как капризный ребёнок, из коробки ему подавай SSL и никакой Basic аутентификации, файлы не больше 50 мегабайт, в папках не более 500-1000 файлов.
Увы, но даже после всего файлы более 4гб передавать не получится. (Скорее всего из за кривой реализации, которая файлы при открытии скачивает в память/на диск чтобы получить более менее стандартный файловый дискриптор, это мои домыслы.)
Вот здесь собраны все настройки с описанием: blogs.msdn.com/b/robert_mcmurray/archive/2008/01/17/webdav-redirector-registry-settings.aspx
Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINESYSTEMCurrentControlSetservicesWebClientParameters] "SupportLocking"=dword:00000000 "InternetServerTimeoutInSec"=dword:0000001e "ServiceDllUnloadOnStop"=dword:00000001 "ServerNotFoundCacheLifeTimeInSec"=dword:0000000a "ClientDebug"=dword:00000000 "FileSizeLimitInBytes"=dword:ffffffff "SendReceiveTimeoutInSec"=dword:0000003c "LocalServerTimeoutInSec"=dword:0000000f "FileAttributesLimitInBytes"=dword:00989680 "AcceptOfficeAndTahoeServers"=dword:00000001 "ServiceDebug"=dword:00000000 "BasicAuthLevel"=dword:00000002
Автор: Ivan_83