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

Наконец-то разобрался со своим провайдером и доказал тех.поддержке, что проблемы с загрузкой Google из-за их глюков в DNS. Теперь все работает как полагается и можно спокойно приступать к работе с Google API.

Начать решил с Google Calendar API по двум причинам.

Во-первых, потому что сам частенько использую этот сервис как напоминалку о ближайших событиях и встречах, когда необходимо получить уведомление в виде SMS (к сожалению, в Хронометре такой функции не предусмотрено).

Во-вторых, думаю в свободное время начать работу над новой версией Хронометра и скрестить его функционал с этим он-лайн сервисом Google. Думаю, получиться должно неплохо. На данный момент в руках уже есть класс, используемый для авторизации в аккаунте, поэтому сразу приступил к работе с целевым API. Итак, что можно сделать, используя Celendar API:

  1. Получить список календарей (весь или только тех, владельцами которых Вы являетесь)
  2. Добавить новый, редактировать или удалить календарь (основной календарь удалить нельзя, судя по описанию API)
  3. Добавить, удалить или редактировать подписчиков
  4. Работать с событиями в календарях
  5. и ещё много чего

В общем работы навалом и это только с одним из списка доступных API Google для использования в настольных приложениях.

Первое о чем стоит в данном случае позаботиться (если планировать разработку модулей для нескольких API) — это предусмотреть единый класс для работы с запросами. Можно было бы пойти более простым путем и взять, например, Indy или Synapse в качестве дополнительных библиотек и спокойно толкать туда-сюда запросы, анализировать результат и т.д. Но, почему-то мне кажется, что это не вариант.  Лучше, наверное, создать свой небольшой класс на основе WinInet для отправки запросов, необходимых для работы с API и не волочь каждый раз за собой библиотеку на 1,5 Mb.

Именно с этого я и начал — с создания своего класса для работы с запросами. При этом необходимо было учесть, что в API Google используются как простые http- так и https-протоколы, а запросы не ограничиваются простыми GET и POST. В том же Celendar API используются дополнительно PUT и DELETE.

Какие функции из WinInet нам нужны? Для того, чтобы подключиться к серверу и, например, скачать информацию нам необходимо последовательно вызвать ряд функций:

  1. InternetOpen — получить дескриптор и использовать его в следующей функции
  2. InternetConnect — создать подключение к серверу и получить дескриптор этого подключения для использовании в следующей функции
  3. HttpOpenRequest — создать новый запрос, опять же получив дескриптор, на этот раз HTTP-запроса, который использовать в функции
  4. HttpSendRequest — отправить запрос, используя дескриптор HTTP-запроса и читать данные функцией InternetReadFile.

Как видите, в самом простом случае надо использовать пять функций. При этом не учитывается то, что нередко (а в случае работы с Google API — постоянно) приходится включать в запрос специфические заголовки, наподобие версии API и т.д., использовать различные параметры, читать ответные заголовки и т.д. В этом случае каждый раз писать по-новой функцию на отправку/получение данных, мягко говоря может вызвать геморрой и несварение желудка.

На данный момент класс THTTPSender имеет следующее описание:

type
THTTPSender = class
private
  FHeaders: TStringList;
  FAgent: string;
  FMethod: string;
  FProtocol: string;
  FPort: integer; //порт
  FDomain: string;   //домен без http
  FParamStr: string; //всё, что стоит в адресе после / кроме параметров
  FParameters: string;
  procedure ParseURL(var cURL:string);
public
  constructor Create;
  function SendRequest(fURL:string):WideString;
  property Headers: TStringList read FHeaders write FHeaders;
  property Agent: string read FAgent write FAgent;
  property Method: string read FMethod write FMethod;
  property Protocol:string read FProtocol write FProtocol;
end;

Пока работа с запросами проходит следующим образом. Например, нам необходимо получить список календарей из своего аккаунта. Для этого в запросе необходимо указать два специфических заголовка:

GData-Version: 2

Заголовок, указывающий какую версию API мы используем.

Authorization: GoogleLogin auth=yourAuthToken

Заголовок для доступа к календарям, где yourAuthToken — это параметр Auth, полученный при авторизации с использованием GoogleLogin.

Отправляем запрос и получаем ответ в виде строки:

with THTTPSender.Create do
begin
  Method:='GET';
  Headers.Add('GData-Version: 2');
  Headers.Add('Authorization: GoogleLogin auth='+FAccount.Auth);
  ResponseText:=SendRequest('http://www.google.com/calendar/feeds/default/allcalendars/full');
end;

Функция SendRequest работает следующим образом:

Анализируем URL:

procedure THTTPSender.ParseURL(var cURL: string);
var lencurl: cardinal;
    aURL: string;
    aURLc: TURLComponents;
begin
  lencurl:=INTERNET_MAX_URL_LENGTH;
  if pos('http',cURL)<=0 then
  cURL:='http://'+cURL;
  SetLength(aURL, lencurl);
//каноникализируем URL
  InternetCanonicalizeUrl(PChar(cURL),PChar(aURL),lencurl,ICU_BROWSER_MODE);
//разбиваем УРЛ на составные части
  with aURLc do
    begin
      lpszscheme := nil;
      dwschemelength := internet_max_scheme_length;
      lpszhostname := nil;
      dwhostnamelength := internet_max_host_name_length;
      lpszusername := nil;
      dwusernamelength := internet_max_user_name_length;
      lpszpassword := nil;
      dwpasswordlength := internet_max_password_length;
      lpszurlpath := nil;
      dwurlpathlength := internet_max_path_length;
      lpszextrainfo := nil;
      dwextrainfolength := internet_max_path_length;
      dwstructsize := sizeof(aurl);
  end;
  if InternetCrackUrl(PChar(aURL), Length(aURL), 0, aURLC) then
    begin
      if aURLc.lpszUrlPath='/' then
        FDomain:=ReplaceStr(aURLC.lpszHostName,'/','')
      else
        begin
          FDomain:=copy(aURLC.lpszHostName,1,length(aURLC.lpszHostName)-length(aURLC.lpszUrlPath));
        end;
  FPort:=aURLc.nPort;
  FParamStr:=aURLc.lpszUrlPath;
  FParameters:=aURLc.lpszExtraInfo;
  case aURLc.nScheme of
    INTERNET_SCHEME_DEFAULT:FProtocol:='HTTP';
    INTERNET_SCHEME_FTP:FProtocol:='FTP';
    INTERNET_SCHEME_HTTP:FProtocol:='HTTP';
    INTERNET_SCHEME_HTTPS:FProtocol:='HTTPS';
    INTERNET_SCHEME_FILE:FProtocol:='FILE';
    INTERNET_SCHEME_MAILTO:FProtocol:='MAILTO';
  end;
end
else
  MessageBox(0, PChar('Ошибка WinInet #'+IntToStr(GetLastError)),'Ошибка', MB_OK);
end;

Все данные, необходимые для осуществления запроса сохраняются в соответствующих полях класса. Далее последовательно выполняем функции:

if FProtocol='HTTP' then
  hConnect:=InternetConnect(hInet, PChar(FDomain), INTERNET_DEFAULT_HTTP_PORT,'anonymous', nil, INTERNET_SERVICE_HTTP, 0, 0)
else
  if FProtocol='HTTPS' then
    hConnect:=InternetConnect(hInet, PChar(FDomain), INTERNET_DEFAULT_HTTPS_PORT,'anonymous', nil, INTERNET_SERVICE_HTTP, 0, 0)
  else
    //уничтожаем дескриптор и выходим
  if hConnect<>nil then
    hRequest:=HttpOpenRequest(hConnect,PChar(FMethod),PChar(FParamStr),nil,nil,nil,INTERNET_FLAG_PRAGMA_NOCACHE or INTERNET_FLAG_RELOAD,0)
  else
   //уничтожаем дескрипторы и выходим

Получили дескриптор hRequest, необходимы для отправки запроса. При этом если протокол не HTTP и не HTTPS, то завершаем работу предварительно убив дескрипторы.

Далее, нам необходимо добавить в запрос свои заголовки. Для того, чтобы добавить в запрос свои заголовки можно воспользоваться функцией HttpAddRequestHeaders или указать свои заголовки непосредственно в функции HttpSendRequest.

В первом случае, в функцию HttpAddRequestHeaders необходимо передать следующие параметры:
hConnect — дескриптор, полученный при вызове HttpOpenRequest
lpszHeaders -указатель на строку, содержащую заголовки, при это каждый заголовок отделяется символами #10#13
dwHeadersLength — общая длина заголовков (в символах)
dwModifiers — флаги-модификаторы. Для нас достаточно использование флага HTTP_ADDREQ_FLAG_ADD.

Ну, а дальше используем уже известный, наверное каждому, кто имел дело с WinInet алгоритм чтения данных:

if HttpSendRequest(hRequest,nil,0,PChar(FParameters),length(PChar(FParameters)))then
begin
  SetLength(Buff, 1);
  B:=false;
  I:=1;
  while true do
    begin
      DataAvailable(hRequest, L);
      if L = 0 then break;
      SetLength(Buff, I + L);
      B:=InternetReadFile(hRequest, @Buff[I], L, ReadedSize);
      if NOT B then break;
      inc(I, ReadedSize);
    end;
  Buff[I] := #0;
  if B then
    begin
     result := WideString(Buff);
    end
  else 
    result := '';
end;

Пока использовал класс в работе с запросами GET к календарям Google. Вроде бы видимых косяков не нашел. Как доработаю полностью — выложу в доступ.

Теперь, что касается непосредственно API. На данный момент класс для работы с календарями умеет совсем немного — получать доступ к аккаунту и получать информацию по всем календарям. Выглядит он, соответственно, проще простого:

type
  TCelendar = record
    id: string;
    etag:string;
    Author:string;
    Title: string;
    Description: string;
    timezone:string;
    hidden:boolean;
    Color:string;
    selected:boolean;
    accesslevel:string;
    where:string;
end;
 
type
 TCelendarList = array of TCelendar;
 
type
  TGoogleCalendar = class
private
  FAccount: TGoogleLogin;
  FCelendars: TCelendarList; //календари пользователя
  function GetCelendars: TCelendarList;
public
  constructor Create(const email, password: string);
  function Login: boolean;
  property Account: TGoogleLogin read FAccount write FAccount;
  property Celendars:TCelendarList read GetCelendars;
end;

Те, кто читал заметку про ClientLogin, поймут без проблем, что такое TGoogleLogin. При этом вызов функции Login возвращает нам только true или false в зависимости от результата авторизации, а вся информация, в т.ч. и по каптче (если её ввод требуется) спокойно хранятся в поле FAccount.

При чтении списка календарей происходит анализ небольшой XML-документа и заполнение массива TCelendarList. Сейчас буду дорабатывать класс для работы с запросами и сразу его испытывать при работе с API Google.

Кстати, информация для тех, кто скачивал модуль GoogleLogin. В модуле есть небольшой косяк —  строку 155 необходимо изменить на

if pos('AUTH',UpperCase(List[i]))>0 then

иначе параметр Auth не читается. А в целом вроде бы больше проблем не наблюдалось.

На сегодня все. Ждите продолжения :).

0 0 голоса
Рейтинг статьи
уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.
Подписаться
Уведомить о
9 Комментарий
Межтекстовые Отзывы
Посмотреть все комментарии
Гимаев Наиль
Гимаев Наиль
09/02/2010 04:02

Вот уж не проблема прикрутить к Хронометру отправку SMS. Хотя бы вызывать SimpleSMS через ShellExecute. Проблема — где достать второй сотовый, чтобы присоединить его к компьютеру, и который будет отправлять SMS.
C ГуглКалендарь удобно работать через файлы календарей (ics), это позволяет синхронизировать совместную работу множества календарных программ.
ссылка: http://www.simplesms.ru/

Алексей Тимохин

Класс, спасибо! =)

Максим Коробов
Максим Коробов
09/02/2010 12:23

А с Google Translate API вы собираетесь разобраться? :)

dobrovestnik ru
09/02/2010 22:05

Автор, опять велосипед изобретаем. на питоне это в разы меньше и проще.
А Google Translate API в Делфи уже реализован http://code.google.com/p/imadering/source/browse/branches/to_delphi_2010/GtransUnit.pas
Спасибо Эдуарду Толмачёву.
 

Максим Коробов
Максим Коробов
10/02/2010 11:17

dobrovestnik ru,

Cпасибо за то, что сообщили насчёт готового модуля. Было бы просто не удобно перед Vlad’ом, если бы это обнаружилось после того, как он приступил бы к исследованиям.
Насчёт велосипеда — данный труд однозначно не зря. Как минимум по той причине, что перенос с языка на язык иногда бывает довольно трудным процессом. Я лично столкнулся с проблемами в адресной арифметике, когда писал код на CryptoAPI с упрощёным примером на C++. И это было не сладко :)

Иван
Иван
18/02/2012 02:39

Возможно, нубский вопрос, но я не могу понять: 
для чего после вызова InternetCrackUrl, проверяетсяa URLc.lpszUrlPath?