Delphi programming blog
Источник: http://teran.karelia.pro/articles/item_4444.html
 

Работа с API vkontakte

Опубликовано 07.11.2010 г. 21:28
Задумывал тут было написать один плагин для работы с упомянутой соц. сетью, да не вышло ничего. Однако реализовал, на мой взгляд, достаточно удобный способ общения с API. Кстати, первый раз прочитал о том какие возможности этот API предоставляет, возможности убоги, то что надо полностью отсутствует.
Итак о чем здесь речь. Чтобы получать данные от сервера необходимо формировать к нему запросы в определенном виде. И сейчас перед вами маленькая статья, о том, как это достаточно удобно можно делать. Для каждого запроса требуется передавать его сигнатуру - хэш md5 для параметров и доп информации, отсортированных в алфавитном порядке. Конечно, запрашивая информацию у сервера, мы можем каждый раз вручную формировать корректный URL с сигнатурой, но ведь это утомительно и неинтересно. Давайте объявим класс, который будет реализовывать подключение к серверу, и запрашивать у него информацию, пусть этот класс называется, например, TSession:
    TSession = class
      strict private
        http : TIdHttp;
        timestamp : int64;

        md5  : TIdHashMessageDigest5;
        xmlDoc : IXMLDocument;

        sessionID     : string;    //session ID
        sessionSecret  : string;   //secret code
        loggedOnUserID  : string;  //UserID

        FMethod : string;
        Connected : boolean;

        procedure setMethod(const newValue : string);
      public
        urlParams : TStringList;
        xmlRoot : IXMLNode;

        constructor Create();
        destructor Destroy();

        procedure RequestData();
        function  GetBinaryData(const url : string): TMemoryStream;

        procedure setSessionData(const  sid, secret, uid : string);

        property method : string read FMethod write setMethod;
    end;
А теперь по порядку рассмотрим члены класса:
  1. http:TIdHttp обеспечивает отправку URL запросов серверу.
  2. timestamp - API накладывает ограничение - не более трех запросов в секунду, поэтому нам не помешает проверять, как давно был отправлен последний запрос, и не позволят отправлять запрос чаще чем раз в 330мс
  3. md5 - необходим для формирования хэша сигнатуры запроса.
  4. xmlDoc - ответ сервера мы будем получать в формате XML, поэтому объекта IXMLDocument нам понадобится для хранения и обработки результата.
  5. SessionID, sessionSecret & LoggedOnUserID - идентификаторы полученные при авторизации (ID сессии, секретный код сессии и ID пользователя в соц. сети.)
  6. FMethod - строка представляющая API метод сервера.
  7. connected - свойство определяющее выполнено ли подключение (заданы ли ID сессии и т.п)
  8. urlParams - наши параметры к методам API
  9. xmlRoot - корень документа полученного в ответе сервера
  10. Конструктор и деструктор, реализуют создание и освобождение объектов описанных выше.
  11. RequestData() & GetBinaryData - методы, выполняющие запрос к серверу, при установленных параметрах.
  12. setSessionData - устанавливает параметры сессии
  13. method - свойство для установки метода API
Также объявим некоторые константы (названия думаю очевидны):
      APP_ID        = '1xxxxxxx';
      API_VERSION = '3.0';
      API_FORMAT  = 'XML';
      API_URL     = 'http://api.vkontakte.ru/api.php?';
А теперь более подробно о реализации: конструктор и деструктор создают и соответственно освобождают объекты http, md5, urlParams, на этом их функция заканчивается. Далее для установки параметров сессии необходим вызов метода setSessionData, который устанавливает значения SessionID, sessionSecret & loggedUserID, а также устанавливает значение переменной connected в значение true. Теперь более интересное: как уже понятно, то url параметры у нас будут храниться в соответствующем списке. Поэтому при смене метода API старые параметры необходимо удалять. Для этого мы реализовали свойство method и процедуру его назначения:
procedure TSession.setMethod(const newValue: string);
begin
    FMethod := newValue;
    urlParams.Clear();
end;
Иногда нам требуется получать бинарные данные - например изображения (фото), при этом не требуется обращаться к методам API а напрямую запрашивать apache о выдаче соответствующего файла. Для таких целей у нас имеется соответствующий метод getBinaryData, который возвращает результат в виде потока, для заданного URL:
function TSession.GetBinaryData(const url: string): TMemoryStream;
begin
    try
        result := TMemoryStream.Create();
        http.Get(url,result);
        result.Position := 0;
    except
        if result  nil then
            result.Free();
    end;
end;
Самое интересное - реализация процедуры получения данных с использованием API - requestData(). Прежде всего опишем в ней следующие локальные переменные (сигнатура запроса, результирующая URL, параметр URL для итерации по списку параметров, штамп текущего времени):
var signature : string;
    requestURL : string;
    param : string;
    tsNow : int64;
Также реализуем вложенную процедуру для формирования сигнатур. Сигнтатура строится в виде хэша от строки состоящей из ID пользователя, списка параметров и их значений, отсортированном в алфавитном порядке и секрета сессии. Поэтому первое что мы сделаем - отсортируем параметры используя urlParams.Sort(). Далее получим строку из параметров, добавим к ней идентификатор пользователя, и секрет сессии. После чего вычислим искомый хэш.
    function CreateSignature():WideString;
    var sigStr : AnsiString;
        i : integer;
    begin
        urlParams.Sort();
        for i:=0 to urlParams.Count - 1 do begin
            sigStr := sigStr + urlParams[i];
        end;
        sigStr := loggedOnUserID + sigStr + sessionSecret;
        result := md5.HashStringAsHex(sigStr);
        result := LowerCase(result);
    end;
Заметьте, исходная строка для формирования сигнатуры - Ansi строка. Значения хэша для такой же Unicode строки будет совсем иным. Результат также следует привести к нижнему регистру. Давайте теперь рассмотрим код основной части функции RequestData(). Сначала добавим к списку параметров необходимые: формат выдачи данных, версию API, установленный API метод, и ID приложения. Затем сформируем сигнатуру.
begin
    if not Connected then exit;

    with urlParams do begin
        values['method'] := FMethod;
        values['api_id'] := APP_ID;
        values['format'] := API_FORMAT;
        values['v'] := API_VERSION;
    end;
    signature := CreateSignature();
Теперь сформируем полную URL запроса: базовая URL, идентификатор сессии, сигнатура запроса, и все наши необходимые параметры:
    requestURL := API_URL +
                 'sid='  + SessionID +
                 '&sig=' + Signature;

    for param in urlParams do begin
        requestURL := requestURL + '&' + param ;
    end;
Проверим, не часто ли мы отправляем запросы, и если надо подождем необходимое время. Для этого используем структуру TStopWatch из модуля Diagnostics.
    tsNow := TStopwatch.GetTimeStamp;
    if (tsNow - timestamp ) < 350000 then sleep(333);
    timeStamp := TStopwatch.GetTimeStamp;
Как я уже говорил, отправлять можно не более трех запросов в секунду. Поэтому, если если время последнего обращения к серверу и текущее различаются менее чем на 0.3 сек, то нам надо дождаться до истечения этих 0.3сек. Вообще время ожидания в процедуре sleep должно быть (333 - (tsNow - timeStamp) div 1000), т.е ждать надо ровно столько, сколько необходимо до перерыва в 0.3 сек, а не именно 0.3 секунды. Но именно в такой записи сервер все равно может выдавать ошибки, поэтому сей параметр надо как то подбирать более детально. Теперь мы передадим нашу строку запроса на сервер, получим в ответ XML данные (а может быть и ошибку подключения), настроим xmlRoot на корень документа, проверим не ошибочным ли этот документ является.
    try
        xmlDoc.XML.text :=  http.Get(requestURL);
        xmlDoc.Active := true;
        xmlRoot := xmlDoc.DocumentElement;

        if xmlRoot.NodeName = 'error' then
            messageDlg('RequestData error:'#13 +
                       'params: ' + urlParams.Text + #13+
                        xmlRoot.XML, mtError, [mbOk],0);
    except
        //
    end;
В блоке except мы можем отловить как ошибки подключения к серверу, так и ошибки API (неправильный запрос и т.п), проверив имя корневого узла.

А теперь давайте вернемся к тому, зачем все это.

Допустим в нашем приложении имеется memo в которое мы хотим помещать текст ответа сервера, при запуске приложения мы успешно создали экземпляр класса TSession, авторизовались на ресурсе (процедуру авторизации можно, что впрочем логично, встроить в данный класс, и тогда убрать метод setSessionData() ). Теперь рассмотрим типовой обработчик нажатия кнопки на форме, который будет используя наш объект session : TSession заполнять соответствующий TMemo, запрашивая данные профиля пользователя:
procedure TMainForm.QueryProfileDataButtonClick(Sender: TObject);
begin
    with session do begin
        method := 'getProfiles';
        urlParams.values['fields'] := 'uid,first_name,last_name,nickname';
        urlParams.Values['uids']   := '1xxxxxxx';
        requestData();
    end;
    resultMemo.text := session.xmlRoot.XML;
end;
выходит вполне компактно и удобно.
Метки:  vkontakte API 

Комментарии

Антон
08.11.2010 в 13:58
Ваш подход намного проще и удобнее, чем у автора блога http://devdelphi.ru.
ter
09.11.2010 в 14:41
ага, читал как там сделано.
- Имя
- e-mail*
- Сайт
вы можете использовать теги [i],[b],[code],[quote]
Дополнительно