уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.

Недавно Яндекс объявил о запуске свого облачного сервиса под названием Яндекс.Диск. А буквально пару дней назад и 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. Я же, в заключении, приведу пару полезных ссылок по теме:

  1. Документация по API Яндекс.Диска
  2. Спецификация протокола WebDAV
Скачать исходник: Исходники —> API онлайн-сервисов —> Яндекс API

Книжная полка

Описание: Рассмотрены практические вопросы по разработке клиент-серверных приложений в среде Delphi 7 и Delphi 2005 с использованием СУБД MS SQL Server 2000, InterBase и Firebird. Приведена информация о теории построения реляционных баз данных и языке SQL. Освещены вопросы эксплуатации и администрирования СУБД.
купить книгу delphi на ЛитРес
Описание: Рассмотрены малоосвещенные вопросы программирования в Delphi. Описаны методы интеграции VCL и API. Показаны внутренние механизмы VCL и приведены примеры вмешательства в эти механизмы. Рассмотрено использование сокетов в Delphi: различные режимы их работы, особенности для протоколов TCP и UDP и др.
купить книгу delphi на ЛитРес
0 0 голоса
Рейтинг статьи
уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.
Подписаться
Уведомить о
29 Комментарий
Межтекстовые Отзывы
Посмотреть все комментарии
samsim
02/05/2012 20:34

Эта штука ужа работает но еще дает много ошыбок. Для его правильной работы нужно время.

Алексей
Алексей
17/06/2012 00:10

Очень много времени прошло, но все равно Яндекс.Диск в стадии бета, номера сборок возрастают со скоростью черепахи, возможности у него ИМХО слабенькие…
Как вы думаете, что ждет Яндекс.Диск?

Алексей
Алексей
17/06/2012 01:32

Во всяком случае для меня их клиент черезчур агрессивен. Загружается при старте и пытается обновиться. И нормально это отключить нельзя, максимум — выдавать запрос на установку новых оновлений.
По поводу: «Диск будет пользоваться успехом у пользователей, которые предпочитают Яндекс». Опять же ИМХО, будь это не Яндекс, не было бы такого успеха, я даже не открыл бы ег оне будь это Яндекс, т.к. ставил цель лишь посмотреть возможности.

Олег
Олег
09/07/2012 22:12

Влад, здравствуйте!
Во-первых, большое спасибо за блог и за статьи! Очень много нужного и полезного материала.

Я только-только слез с Indy и начал разбираться в Synapse. И возникла проблема.
Когда я отправляю запрос на сервер, то в сниффере строка адреса выглядит так: «http://localhost:80».

Можно ли как-то скрыть «:80»?
P.S. AddPortNumberToHost:=False; но я так понимаю, это только для заголовков?

Скриншот:comment image

Олег
Олег
10/07/2012 01:34

Спасибо за ответ, Влад!
Я имел ввиду, что в адрес добавляется дефолтный 80й порт. Сделал «костыль»)
Т.к. я использую только 80й порт в своём проекте, то изменил строку в модуле httpsend.pas

if UsingProxy then
URI := Prot + ‘://’ + s + ‘:’ + Port + URI;

на

if UsingProxy then
URI := Prot + ‘://’ + s + URI;

Результат меня вполне устраивает :)

Роман
Роман
12/07/2012 20:57

У меня не работает приложение из статьи, метот PROPFIND

dimka
dimka
17/07/2012 18:53

А как скачать файл с Яндекс.Диска и открыть его в Windows, например PDF?
Хочется примерчик.

loginza7okyFLogXaZ5exDvRaW3SR

Реализация API на C#: http://yadi.sk/d/zBTSe50Bqaq0

Inna
Inna
09/11/2012 15:28

Спасибо за информацию)

Антон Исаев
14/03/2013 14:35

Для тех, кто будет дальше копаться в этой теме: для «свежих» аккаунтов при попытке загрузить файл на Яндекс.Диск HTTPMethod может выдавать Internal Error. Решение — либо вручную загрузить 1-2 файла через web-интерфейс Яндекс.Диска, либо подключить Яндекс.Диск как сетевой диск в Вашей ОС и также загрузить в папку 1-2 файла. После этого загрузка на Яндекс.Диск файлов проходит без проблем.

RaJa
23/10/2013 00:03

А можете показать пример для 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;

RaJa
23/10/2013 02:05
Ответить на  RaJa

А, все, разобрался

Андрей
Андрей
09/05/2014 04:13
Ответить на  RaJa

не могбы выложить рабочий код?

trackback

[…] про работу с API различных сервисов типа Google Drive, Яндекс.Диск и пр. Хотя, если большому количеству народа такая […]

Андрей
Андрей
09/05/2014 04:12

А не могли бы написать пример для PUT а то что то не получается,возвращает 400.

Олег
Олег
12/05/2014 20:52

Скажите пожайлусто как получить публичную ссылку на файл???Буду очень благодарен.

Олег
Олег
13/05/2014 18:46
Ответить на  Vlad

я попытался отправить вам напоминание на почту но мне пишет что такого адреса не существует либо он заблокирвоан

Андрей
Андрей
14/05/2014 14:32

Если кому нужно то вот рабочий код на 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;

Юра
Юра
27/05/2014 11:12
Ответить на  Андрей

Полезный комментарий!
Только не совсем работает.
Если подправить то компилируется, и даже файл создается, но он остается пустым, то есть данные не загружаются.
Вот код:

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;

Владимир
Владимир
29/09/2014 06:45
Ответить на  Юра

День добрый, у меня такая же проблема: что необходимо добавить в Headers, чтобы файл на диске заполнился? Заношу в заголовок все, что просит Яндекс-API и только пустой файл и ошибка 499. Document.LoadFromFile вызываю до вызова запроса PUT, иначе выдается ошибка при отправке самого запроса.

Михаил
Михаил
06/08/2014 09:20

как заменить вот такое объявление, чтобы компилировалось в Delphi 7?

TWDResourceList = class(TList)
public
constructor Create;
destructor Destroy;override;
procedure Clear;
end;

Михаил
Михаил
06/08/2014 09:46

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