Недавно Яндекс объявил о запуске свого облачного сервиса под названием Яндекс.Диск. А буквально пару дней назад и Google запустил свой Google Drive. У меня была возможность поработать с обоими этими сервисами и оценить их удобство/полезность для себя. Надо сказать, что при выборе тех или иных онлайн-сервисов я, обычно, выбирал сервисы от Google (Календарь, Почта, соц.сеть и т.д.), но в случае с облачными сервисами я пока больше склоняюсь к использованию сервиса от Яндекс. По большей части такой выбор был сделан на основе изучения двух документов: API Яндекс.Диска и SDK Google Drive. Google в очередной раз «порадовал» ограничениями на использование API и набором методов API — есть методы добавления, редактирования и получения данных по файлам, но почему-то отсутствуют методы для удаления…странно как-то. Можно было бы привести ещё несколько причин моего выбора, но к статье эти причины не имеют никакого отношения. Яндекс в плане API оказался более дружелюбным к разработчикам — никаких ограничений, доступ к методам через Basic- или OAuth-аутентификацию (по выбору) и никаких «наворотов» в API — только методы и возможности WebDAV. Надеюсь, что Яндекс после окончания бетта-тестов Диска не переделает API и то, что будет рассказано и показано ниже будет работать ещё долго. Итак, сегодня продолжим речь о замечательной библиотеке Synapse и посмотрим как можно работать с протоколом WebDAV на примере API Яндекс.Диска.
Что представляет из себя протокол WebDAV? Вот небольшая выдержка из Вики:
WebDAV (Web-based Distributed Authoring and Versioning) — защищённый сетевой протокол высокого уровня, работающий поверх HTTP для доступа к объектам и коллекциям.
WebDAV расширяет HTTP следующими командами:
- PROPFIND — Получение свойств объекта на сервере в формате XML. Так же можно получать структуру репозитория (дерево каталогов).
- PROPPATCH — Изменение свойств за одну транзакцию.
- MKCOL — Создать коллекцию объектов (каталог в случае доступа к файлам)
- COPY — Копирование из одного URI в другой
- MOVE — То же что и предыдущий, только перемещение
- LOCK — Поставить блокировку на объекте. WebDAV поддерживает эксклюзивные и общие (shared) блокировки
- UNLOCK — Снять блокировку с ресурса
А раз WebDAV — это надстройка над HTTP(S), то нам никто не мешает использовать для этого давно уже нам известный класс THTTPSend. Все, что от нас требуется при подготовке к работе — это немного вспомнить про работу с https в Synapse, ну и, если необходимо, освежить в памяти работу с GZip. Так как WebDAV передает всю мета-информацию по документам в виде XML, то дополнительно нам может потребоваться какая-нибудь библиотека для работы с XML — обычный MSXML, NativeXML и т.д.
Примечание: если ищите быстрый парсер XML — почитайте эту статью в блоге Teran’а — там есть хорошая табличка сравнения скорости работы различных XML-парсеров
Теперь приступим к работе. Создадим в Delphi проект VCL Application, подключим в uses модули Synapse:
- httpsend,
- synacode,
- ssl_openssl
И на главной форме разместим следующие компоненты (см. рисунок):
Для доступа к методам API будем использовать простую Basic-аутентификацию. Так как в каждый запрос мы должны будем вставлять заголовок аутентификации, то напишем свой небольшой класс для работы с API. В принципе, можно было бы обойтись пока и без класса, но лишним, думаю, он не будет. Итак, заготовка класса для работы с WebDAV через THTTPSend будет такой:
type TWebDAVSend = class private FHTTP : THTTPSend; FToken: AnsiString; FPassword: string; FLogin: string; procedure SetLogin(const Value: string); procedure SetPassword(const Value: string); procedure SetToken; public constructor Create; destructor Destroy; override; property Login: string read FLogin write SetLogin; property Password: string read FPassword write SetPassword; end; { TWebDAVSend } constructor TWebDAVSend.Create; begin inherited; FHTTP:=THTTPSend.Create; end; destructor TWebDAVSend.Destroy; begin FHTTP.Free; inherited; end; procedure TWebDAVSend.SetToken; begin FToken:=EncodeBase64(FLogin+':'+FPassword); end; procedure TWebDAVSend.SetLogin(const Value: string); begin FLogin := Value; SetToken; end; procedure TWebDAVSend.SetPassword(const Value: string); begin FPassword := Value; SetToken; end;
Теперь попробуем реализовать несколько методов WebDAV. Первое, что нам необходимо — это определить какие каталоги имеются в Яндекс.Диске. Для этого нам надо реализовать в классе метод API PROPFIND. Согласно документации API, набор файлов и каталогов, свойства которых должны содержаться в ответе, определяется заголовком Depth со следующими поддерживаемыми значениями:
- 0 — запрашиваются свойства файла или каталога, непосредственно указанного в запросе.
- 1 — запрашиваются свойства каталога, а также всех элементов, находящихся на первом уровне каталога.
В результате запроса сервер ответит нам XML-документом, содержащим необходимые нам свойства. Реализация PROPFIND в нашем классе будет такой:
function TWebDAVSend.PROPFIND(Depth: integer; const Element: String): string; begin with FHTTP do begin Headers.Clear; Document.Clear; Headers.Add('Authorization: Basic ' + FToken); Headers.Add('Depth: ' + IntToStr(Depth)); Headers.Add('Accept: */*'); if HTTPMethod('PROPFIND', GetRequestURL(Element)) then result := ReadStrFromStream(Document, Document.Size) else raise Exception.Create(rsPropfindError+' '+ResultString); end; end;
Рассмотрим работы этой функции. Вначале мы очищаем заголовки и тело от данных, полученных в предыдущем запросе, если таковой был. После этого вставляем заголовок аутентификации, указываем «глубину просмотра» в заголовке Depth. Затем, выполняем запрос PROPFIND на сервер и здесь вам встречается неизвестная функция GetRequestURL.
Функция GetRequestURL получает корректный URL. В качестве параметра задается любой действительный путь в дереве каталогов и файлов. К примеру, в параметре функции я могу задать такую строку:
Documents/Мои статьи/Delphi/Работа с API Яндекс.Диска.doc
И функция вернет мне URL, который гарантированно примет сервер. GetRequestURL выглядит следующим образом:
const cWebDAVServer = 'https://webdav.yandex.ru/'; function TWebDAVSend.GetRequestURL(const Element: string): string; var URI: string; begin URI:=Element; if URI[1]='/' then Delete(URI,1,1); Result:=cWebDAVServer+EncodeUTF8URI(URI); end;
Здесь, опять же, встречается ещё одна непонятная функция — EncodeUTF8URI. Эта функция проводит кодирование URI, который может содержать символы в кодировке UTF-8. Выглядит функция так:
function TWebDAVSend.EncodeUTF8URI(const URI: string): string; var i: integer; Char: AnsiChar; begin result := ''; for i := 1 to length(URI) do begin if not(URI[i] in URLFullSpecialChar) then begin for Char in UTF8String(URI[i]) do Result:=Result+'%'+IntToHex(Ord(Char), 2) end else Result:=Result+URI[i]; end; end;
URLFullSpecialChar — это множество, которое описано в модуле synacode.pas Synapse:
URLFullSpecialChar: TSpecials = [';', '/', '?', ':', '@', '=', '&', '#', '+'];
Вполне возможно, что можно было бы как-нибудь обойтись методами Synapse типа EncodeURLElement, но для строки «Библиотека» эта функция выдавала такую строку:%C1%E8%E1%EB%E8%EE%F2%E5%EA%E0
вместо такой:%D0%91%D0%B8%D0%B1%D0%BB%D0%B8%D0%BE%D1%82%D0%B5%D0%BA%D0%B0
поэтому я, долго не заморачиваясь, написал свою маленькую функцию кодирования. Она может и не идеальна, но сервер Яндекс.Диска отвечал правильно. Так…с чего мы начали? Ах да — с TWebDAVSend.PROPFIND. Так вот, если запрос проходит успешно, то дальше мы просто считываем строку из тела запроса (Document), а если была какая-либо ошибка — показываем её пользователю.
В результате выполнения TWebDAVSend.PROPFIND сервер может ответить следующим образом:
- Если запрос обработан успешно — возвращается XML-документ, содержащий свойства элемента(-ов)
- Если был задан не верный элемент, то вернется строка, содержащая описание ошибки.
Пример успешно выполненного запроса PROPFIND показан на рисунке ниже:
Пример с неправильно заданным элементом (сервер возвращает код 200 и описание ошибки):
Про метод PROPFIND стоит также добавить, что имя каталога на сервере может не совпадать с представлением этого каталога в URL. Например, каталог «Музыка» в URL представляется как «Music». Чтобы понять как должен выглядеть URL для получения свойств какого-либо каталога необходимо смотреть на свойство ‘href’ в XML-документе. Список всех свойств, поддерживаемых в рамках протокола WebDAV приведен в разделе описания протокола DAV Properties.
Аналогично методу PROPFIND можно реализовать и другие методы для работы с WebDAV в Synapse. Все методы рассматривать, думаю, смысла нет — алгоритм работы практически неизменный: вставили заголовки аутентификации, собрали URL, отправили запрос, обработали ответ. Но, для полноты картины, рассмотрим ещё один метод WebDAV — MKCOL. В отличие от предыдущего, этот метод не возвращает ничего в теле ответа, а об успешности выполнения запроса можно судить по ResulCode. Итак, чтобы создать на сервере новую коллекцию, мы будем использовать в нашем классе такую функцию:
function TWebDAVSend.MKCOL(const ElementPath: string): boolean; begin Result:=False; with FHTTP do begin Headers.Clear; Document.Clear; Headers.Add('Authorization: Basic ' + FToken); Headers.Add('Accept: */*'); if HTTPMethod('MKCOL', GetRequestURL(ElementPath)) then begin Result:=ResultCode=201; if not Result then raise Exception.Create(IntToStr(ResultCode)+' '+ResultString); end else raise Exception.Create(rsPropfindError+' '+ResultString); end; end;
Пример использования MKCOL:
MKCOL('Documents/Новая папка с документами');
В результате в каталоге Documents будет создан новый с названием «Новая папка с документами». Следует отметить, что согласно протоколу WebDAV, в результате одного запроса может быть создан только один каталог. Если приложение отправляет запрос о создании каталога a/b/c/, а в каталоге a/ нет каталога b/, то сервис не создает каталог b/, а отвечает c кодом 409 Conflict.
Вот, пожалуй, кратко о том как можно реализовать работу с WebDAV в Synapse и использовать API Яндекс.Диска в своих Delphi-приложениях. Исходник проекта, рассмотренного в статье Вы всегда сможете скачать со страницы с исходниками и, при необходимости, дописать реализацию других методов API. Я же, в заключении, приведу пару полезных ссылок по теме:
Книжная полка
Описание: Рассмотрены практические вопросы по разработке клиент-серверных приложений в среде Delphi 7 и Delphi 2005 с использованием СУБД MS SQL Server 2000, InterBase и Firebird. Приведена информация о теории построения реляционных баз данных и языке SQL. Освещены вопросы эксплуатации и администрирования СУБД.
|
||
Название: О чем не пишут в книгах по Delphi
Описание: Рассмотрены малоосвещенные вопросы программирования в Delphi. Описаны методы интеграции VCL и API. Показаны внутренние механизмы VCL и приведены примеры вмешательства в эти механизмы. Рассмотрено использование сокетов в Delphi: различные режимы их работы, особенности для протоколов TCP и UDP и др.
|
Эта штука ужа работает но еще дает много ошыбок. Для его правильной работы нужно время.
Очень много времени прошло, но все равно Яндекс.Диск в стадии бета, номера сборок возрастают со скоростью черепахи, возможности у него ИМХО слабенькие…
Как вы думаете, что ждет Яндекс.Диск?
Алексей, я бы не сказал, что с момента запуска Диска прошло много времени. Если смотреть в сторону того же Гугла, то API Google Tasks была в бетте порядка года, а то и больше и даже сейчас сохраняет ограничения бетта-версии для пользователей (хотя вроде как уже API «вышел в свет»). Думаю, что Диск будет пользоваться успехом у пользователей, которые предпочитают Яндекс. Сам храню там большой архив разных документов и пока никаких проблем не испытывал.
Во всяком случае для меня их клиент черезчур агрессивен. Загружается при старте и пытается обновиться. И нормально это отключить нельзя, максимум — выдавать запрос на установку новых оновлений.
По поводу: «Диск будет пользоваться успехом у пользователей, которые предпочитают Яндекс». Опять же ИМХО, будь это не Яндекс, не было бы такого успеха, я даже не открыл бы ег оне будь это Яндекс, т.к. ставил цель лишь посмотреть возможности.
Влад, здравствуйте!
Во-первых, большое спасибо за блог и за статьи! Очень много нужного и полезного материала.
Я только-только слез с Indy и начал разбираться в Synapse. И возникла проблема.
Когда я отправляю запрос на сервер, то в сниффере строка адреса выглядит так: «http://localhost:80».
Можно ли как-то скрыть «:80»?
P.S. AddPortNumberToHost:=False; но я так понимаю, это только для заголовков?
Скриншот:
Олег, приветствую! По поводу вопроса немного не понятно. Дело в том, что все запросы в THTTPSend отправляются через метод HTTPMethod и в нем первым параметром вы указываете URL, а внутри HTTPMethod уже идёт разбор того, что вы указали в параметрах. А AddPortNumberToHost:=False всего-лишь делает так, что в случае, если порт НЕ 80, то его номер НЕ будет добавлен в заголовки. Если порт вообще не указан, то по дефолту в раборе URL (см. function ParseURL в synautil) он автоматом становится 80 и используется дальше именно этот порт…Может в этом дело? Ну или второй вариант откуда берется ’80’ в сниффере — сам… Подробнее »
Спасибо за ответ, Влад!
Я имел ввиду, что в адрес добавляется дефолтный 80й порт. Сделал «костыль»)
Т.к. я использую только 80й порт в своём проекте, то изменил строку в модуле httpsend.pas
if UsingProxy then
URI := Prot + ‘://’ + s + ‘:’ + Port + URI;
на
if UsingProxy then
URI := Prot + ‘://’ + s + URI;
Результат меня вполне устраивает :)
У меня не работает приложение из статьи, метот PROPFIND
Роман, очень странно, что не работает. Проверил ещё раз — никаких изменений в API не было — программа корректно вернула ответ на запрос PROPFIND. А как не работает у вас метод? Что пробовали искать? Если вылетает ошибка, то скажите какая ошибка (код, если есть, описание и т.д.) Я это к чему спрашиваю: если вы, используя программу, попробуете получить ответ на PROPFIND, например, по строке запроса «Музыка», то API (не программа) ответит вам «Not Found», т.к. эта папка системная и имеет название «Music», а «Музыка» — это просто отображаемое в интерфейсе Яндекс.Диска имя папки. Чтобы узнать какие ресурсы лежат у вас… Подробнее »
А как скачать файл с Яндекс.Диска и открыть его в Windows, например PDF?
Хочется примерчик.
Реализация API на C#: http://yadi.sk/d/zBTSe50Bqaq0
Спасибо за информацию)
Для тех, кто будет дальше копаться в этой теме: для «свежих» аккаунтов при попытке загрузить файл на Яндекс.Диск HTTPMethod может выдавать Internal Error. Решение — либо вручную загрузить 1-2 файла через web-интерфейс Яндекс.Диска, либо подключить Яндекс.Диск как сетевой диск в Вашей ОС и также загрузить в папку 1-2 файла. После этого загрузка на Яндекс.Диск файлов проходит без проблем.
А можете показать пример для PUT?
Вот этот метод возвращает ResultString ‘Continue’, файл на сервер не загружается
function TWebDAVSend.PUT(const ElementPath: string; aFileName: string): string;
begin
with FHTTP do
begin
Headers.Clear;
Document.Clear;
Headers.Add(‘Authorization: Basic ‘ + FToken);
Headers.Add(‘Accept: */*’);
Headers.Add(‘Expect: 100-continue’);
Headers.Add(‘Content-Type: application/binary’);
Headers.Add(‘Transfer-Encoding: chunked’);
Document.LoadFromFile(aFileName);
if HTTPMethod(‘PUT’, GetRequestURL(ElementPath)) then
result := IntToStr(ResultCode) //ReadStrFromStream(Document, Document.Size)
else result:=ResultString;
end;
end;
А, все, разобрался
не могбы выложить рабочий код?
[…] про работу с API различных сервисов типа Google Drive, Яндекс.Диск и пр. Хотя, если большому количеству народа такая […]
А не могли бы написать пример для PUT а то что то не получается,возвращает 400.
Скажите пожайлусто как получить публичную ссылку на файл???Буду очень благодарен.
Олег, я комментарии читаю и стараюсь на них отвечать по мере сил и возможностей :) По поводу PUT скажу как есть — в ближайшую неделю-две точно не смогу помочь, т.к. сильно занят в оффлайне и времени свободного практически нет. Если можете подождать, то нет проблем — напомните мне про эту тему на vad383@mail.ru о своей просьбе — соберем примерчик
я попытался отправить вам напоминание на почту но мне пишет что такого адреса не существует либо он заблокирвоан
странно, адрес 100% мой, правильный и рабочий. Ну, если не получается на этот адрес, то скиньте на адрес сайта — admin @ webdelphi .ru (уберите пробелы)
Если кому нужно то вот рабочий код на PUT
function TWebDAVSend.PUT(const ElementHref: string; aFileName: string): string;
begin
with FHTTP do
begin
Headers.Clear;
Document.Clear;
Headers.Add(‘Authorization: Basic ‘ + FToken);
Headers.Add(‘Accept: */*’);
Headers.Add(‘Expect: 100-continue’);
Headers.Add(‘Content-Type: application/binary’);
Headers.Add(‘Transfer-Encoding: chunked’);
if HTTPMethod(‘PUT’, GetRequestURL(ElementHref)) then
begin
result:=’ResultCode=100′;
if result ‘ResultCode=100′ then
raise Exception.Create(IntToStr(ResultCode)+’ ‘+ResultString)
else
Document.LoadFromFile(aFileName);
end
else
raise Exception.Create(rsPropfindError+’ ‘+ResultString);
end;
end;
Полезный комментарий!
Только не совсем работает.
Если подправить то компилируется, и даже файл создается, но он остается пустым, то есть данные не загружаются.
Вот код:
function TWebDAVSend.PUT(const ElementHref: string; aFileName: string): string;
begin
with FHTTP do
begin
Headers.Clear;
Document.Clear;
Headers.Add(‘Authorization: Basic ‘ + FToken);
Headers.Add(‘Accept: */*’);
Headers.Add(‘Expect: 100-continue’);
Headers.Add(‘Content-Type: application/binary’);
Headers.Add(‘Transfer-Encoding: chunked’);
if HTTPMethod(‘PUT’, GetRequestURL(ElementHref)) then
begin
result:=’ResultCode=100′;
//if result = ‘ResultCode=100′ then
//raise Exception.Create(IntToStr(ResultCode)+’ ‘+ResultString)
//else
Document.LoadFromFile(aFileName);
end
else
raise Exception.Create({rsPropfindError+}’ ‘+ResultString);
end;
end;
День добрый, у меня такая же проблема: что необходимо добавить в Headers, чтобы файл на диске заполнился? Заношу в заголовок все, что просит Яндекс-API и только пустой файл и ошибка 499. Document.LoadFromFile вызываю до вызова запроса PUT, иначе выдается ошибка при отправке самого запроса.
как заменить вот такое объявление, чтобы компилировалось в Delphi 7?
TWDResourceList = class(TList)
public
constructor Create;
destructor Destroy;override;
procedure Clear;
end;
В Delphi 7 надо использовать класс TList со всеми вытекающими отсюда последствиями, т.е. писать свои методы чтения/записи элементов, очистки и т.д. С дженериками (как сейчас сделано) на много проще
TWDResourceList = class(TList < TWDResource >)
Переделал этот пример под Lazarus 1.6. Запускал на Windows 7 x64.
Для работы нужно в папку с проектом положить файлы «libeay32.dll» и «ssleay32.dll» соответствующей разрядности (х32 или х64). Скачать их можно с сайта https://indy.fulgan.com/SSL/
Скачать исходник:
https://yadi.sk/d/c9iV-qitqmgQ5