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

Это третья и заключительная часть статьи про работу с API онлайн-сервисов в Delphi. В этой части мы разработаем свое приложение, использующее модуль над которым мы работали в предыдущей части.

Готовое приложение для работы со списком пользователей etxt.ru

На данный момент мы реализовали три метода API:

  1. Получение списка категорий
  2. Получение списка пользователей
  3. Получение подробной информации о пользователе

Этих трех методов вполне достаточно, чтобы написать простенькое приложение для работы со списками пользователей сайта etxt.ru. Пусть интерфейс приложения будет таким:

etxt_api9

Приложение будет работать следующим образом:

  1. Задаем параметры поиска (рейтинг пользователей, онлайн-статус).
  2. Жмем поиск — получаем с сервера всех пользователей, удовлетворяющих параметрам поиска
  3. Все найденные пользователи выводятся в список слева
  4. Выбираем пользователя в списке — получаем подробную информацию о пользователе (функция GetUserInfo2)
  5. Выводим подробную информацию на форму

Что нужно предусмотреть при работе приложения:

  1. Как мы уже определили, когда писали метод получения подробной информации, для тематических категорий пользователя возвращаются только их ID. Следовательно, ещё ДО запроса подробной информации у нас под рукой должен быть список всех категорий (должна выполнится процедура ListCategories)
  2. На данный момент функция ListUsers может возвращать максимум 100 пользователей за раз (это ограничение самого сервиса etxt.ru). Следовательно надо написать метод, который будет работать с ListUsers и возвращать всех пользователей
  3. Необходимо предусмотреть способ прерывания поиска пользователей.

Начнем реализовывать все функции программы по порядку.

Подключаем в uses главной (и единственной) формы приложения наш модуль с классами для работы с API и создаем следующие переменные:

unit main;
 
interface
 
uses
  ..., etxt.api;
 
type
  TForm3 = class(TForm)
    [...]
  private
    Etxt:TEtxtClient;
    ACancel: boolean;
  public
  end;

Как было сказано выше, нам необходимо получить список категорий до того, как мы запросим подробную информацию о пользователе. Например, мы можем это сделать сразу после создания объекта Etxt. Пишем такие обработчики OnCreate() и OnDestroy() для формы:

procedure TForm3.FormCreate(Sender: TObject);
begin
  Etxt:=TEtxtClient.Create;
  Etxt.Token:='СЮДА_ПИШЕМ_СВОЙ_КЛЮЧ_ДОСТУПА';
  Etxt.ApiPass:='СЮДА_ПИШЕМ_СВОЙ_ПАРОЛЬ';
  //получаем список категорий
  Etxt.ListCategories;
end;
 
procedure TForm3.FormDestroy(Sender: TObject);
begin
  Etxt.Free;
end;

Здесь стоит отметить следующий момент:

Сейчас я пишу приложение-пример работы с API, поэтому могу позволить себе упустить некоторые проверки, которые в рабочем приложении необходимо проводить. Так, приведенный выше код вполне рабочий, НО, в рабочем приложении я бы перед выполнением метода ListCategories, как минимум проверил наличие доступа в Интернет.

Итак, первый момент работы программы выполнен — у нас имеется список категорий. Двигаемся далее.

Получаем с сервера всех пользователей, удовлетворяющих параметрам поиска

На момент написания этой статьи количество пользователей etxt.ru составляло более 500 000. Не все, конечно, активные, но тем не менее. Если предположить, что мы зададим такие параметры поиска при которых сервер нам должен будет вернуть всех пользователей, то мы можем получить ситуацию в которой наше приложение «подвиснет» серьезно и на долго. Так, если предположить, что сервер будет нам отвечать за 0,5 сек., а парсинг результатов будет вообще практически моментальным, то на запрос всей базы пользователей у нас уйдет больше 40 минут.

Обычно для таких ситуаций я предусматриваю отдельный поток в приложении, но для примера мы обойдемся более простым решением. У нас имеется переменная ACancel — именно её значение будет нам говорить о том, надо ли прерывать процесс получения списка пользователей или нет, а вся работа приложения будет проходить в одном (основном потоке).

Теперь ещё раз посмотрим на метод ListUsers, которым мы сейчас будем пользоваться:

function ListUsers(ACount, AFrom, ARateFrom, ARateOut: integer; AOnline: boolean):integer;

Как нам отвечает сервер, при задании разных значений входных параметров функции?
Нумерация пользователей в базе сервера начинается с нуля. Следовательно, если мы задаем такие параметры:

function ListUsers(100, 0, 0, 10, True)

то сервер будет обрабатывать наш запрос примерно следующим образом:

  • выберет всех пользователей, который в данный момент находятся на сайте (у нас AOnline = True)
  • из полученного списка отсеет всех пользователей у которых рейтинг более 10 (у нас начальный рейтинг равен 0, конечный — 10)
  • из полученного списка выберет первых 100 пользователей (у нас ACount = 100)
  • выдаст полученный список нам.

При этом, если в конечной выборке окажется меньше 100 пользователей, то сервер вернет нам всех найденных пользователей, т.е. список с сервера может содержать не более ACount записей.

Что будет, если мы зададим такие параметры:

function ListUsers(100, 10, 0, 10, True)

В этом случае сервер выполнит те же действия, что и в предыдущем случае, но в конечный список будет записывать пользователей, начиная с 11-го пользователя (помним, что первый пользователь у нас имеет порядковый номер ноль). Окончательную выборку пользователей в данном случае можно представить так:

etxt_api10Теперь напишем тот самый метод, который будет выдавать нам весь список пользователей, удовлетворяющих заданным нами условиям:

type
  TForm3 = class(TForm)
    [...]
  private
    [...]
    procedure ListAllUsers(ARateFrom, ARateTo:integer; AOnline: boolean);
  public
 
  end;
 
implementation
 
const
  cUserCount = 'Всего: %d чел.';
  cButtonCancel = 'Отмена';
  cButtonFind = 'Поиск';
  cMaxUserCount = 100;
 
procedure TForm3.ListAllUsers(ARateFrom, ARateTo: integer; AOnline: boolean);
var AFrom: integer;
begin
  //указываем, что на первом шаге мы будем получать список с самого начала
  AFrom:=0;
  //Указываем, что поиск должен проводиться
  ACancel:=False;
  //если сервер возвращает на очередном шаге цикла 100 записей
  //и пользователь не нажал "Отмену"
  //то продолжаем запросы
  //если сервер вернет не 100, а 99 или менее записей - значит это был последний ответ с данными 
  //далее сервер будет возвращать пустой список
  //если пользователь нажал кнопку "Отмена", то надо прервать дальнейшие выполнения запросов на сервер
  while (Etxt.ListUsers(cMaxUserCount,AFrom,ARateFrom,ARateTo,AOnline)=cMaxUserCount)and (not ACancel) do
    begin
      inc(AFrom,cMaxUserCount);
      //выводим в Label количество уже найденных пользователей
      lbCount.Caption:=Format(cUserCount,[etxt.UserCount]);
      Application.ProcessMessages;
    end;
  //выводим окончательное количество найденных пользователей
  lbCount.Caption:=Format(cUserCount,[etxt.UserCount]);
  //меняем надпись на кнопке поиска с "Отмена" на "Поиск"
  btnFind.Caption:=cButtonFind;
end;

Комментариев в методе больше, чем кода :). Но, думаю, что так любой желающий сможет разобраться с работой метода. Теперь напишем обработчик события OnClick кнопки «Поиск»:

procedure TForm3.btnFindClick(Sender: TObject);
var I: Integer;
begin
  //если надпись на кнопке "Поиск" - начинаем поиск
  if SameText(btnFind.Caption, cButtonFind) then
    begin
      //меняем надпись на кнопке поиска с "Поиск" на "Отмена"
      btnFind.Caption:=cButtonCancel;
      //очищаем список пользователей от результатов предыдущего поиска
      Etxt.ClearUserList;
      //чистим ListBox 
      listUsers.Items.Clear;
      //ищем всех пользователей по заданным условиям
      ListAllUsers(StrToInt(edRateMin.Text),StrToInt(edRateMax.Text),chkOnline.Checked);
      //начинаем выводить список 
      listUsers.Items.BeginUpdate;
      try
        for I := 0 to Etxt.UserCount-1 do
          lbUsers.Items.AddObject(Etxt.User[i].Fio, Etxt.User[i]);
        //сортируем список по алфавиту
        listUsers.Sorted:=True
      finally
        listUsers.Items.EndUpdate;
      end;
    end
  else
    //надпись на кнопке - "Отмена", значит прерываем поиск
    ACancel:=True;
end;

Теперь можно запустить приложение и посмотреть как работает поиск:
etxt
Как можно видеть на рисунке, по заданным условиям поиска сервер вернул всего 75 пользователей. Теперь научимся выводить подробную информацию о пользователе.

Вывод подробной информации о пользователе

В начале определимся с тем как мы будем работать с тематическими категориями, которые пользователь указывает в своей информации. На данный момент класс TUserInfo содержит только список идентификаторов категорий:

TUserInfo = class(TUser)
private
  [...]
  FCategories: TStrings;
  [...]
public
  [...]
  property CategoryID[AIndex:integer]: string read GetCategoryID;
  [...]
  property CatCount: integer read GetCatCount;
end;

Однако, в самом начале работы над программой мы уже загрузили список всех категорий с сайта. Логичным будет написать небольшую функцию, которая будет проводить поиск категории по её идентификатору. Расположить эту функцию вы можете где угодно. Например, я создал её в классе  TEtxtClient:

TEtxtClient = class(TEtxtAPI)
private
  [...]
public
  function FindCategory(AID: string):TCategory;
  [...]
end;

Функция достаточно просто:

function TEtxtClient.FindCategory(AID: string): TCategory;
var I: Integer;
begin
  Result:=nil;
  for I := 0 to FCategories.Count-1 do
    if FCategories[i].Id=AId then
      Exit(FCategories[i]);
end;

Если в загруженном списке категорий найдется та, у которой значение идентификатора (id) будет равно AID, то она вернется в результате функции, иначе — метод вернет nil.
Теперь у нас есть все необходимые методы для вывода подробной информации о пользователе. Пишем обработчик события OnClick() у списка listUsers:
procedure TForm3.listUsersClick(Sender: TObject);

var UI: TUserInfo;
    I: Integer;
    Cat: TCategory;
begin
  //если выбран какой-либо элемент списка (пользователь)
  if listUsers.ItemIndex>-1 then
    begin
       //очищаем список выполняемых пользователем работ
       listWorks.Items.Clear;
       //очищаем список тематических категорий пользователя 
       listCategories.Items.Clear;
       //запрашиваем информацию о пользователе
       UI:=Etxt.UserInfo2[listUsers.Items.Objects[listUsers.ItemIndex] as TUser];
       //информация была успешно получена
       if Assigned(UI) then
         begin
           //выводим информацию на форму
           lbFio.Caption:=UI.Fio;
           lbLogin.Caption:=UI.Login;
           lbCountry.Caption:=UI.Country;
           lbCity.Caption:=UI.City;
           lbRegdate.Caption:=DateTimeToStr(UI.Regdate);
           lbRate.Caption:=UI.Rate.ToString();
           lbStatus.Caption:=cStatus[UI.Online];
           lbGroup.Caption:=UI.Group;
           //формируем список выполняемых работ
           for I := 0 to UI.WorkCount-1 do
             listWorks.Items.Add(UI.Work[i]);
           //формируем список тематических категорий
           for I := 0 to UI.CatCount-1 do
             begin
               Cat:=Etxt.FindCategory(UI.CategoryID[i]);
               //если нашли категорию в списке
               if Assigned(Cat) then
                 listCategories.Items.Add(Cat.Name)
               //категория не найдена - выводим в список только ID
               else
                 listCategories.Items.Add(UI.CategoryID[i])
             end;
         end;
    end;
end;

Единственный момент в этом методе на который стоит обратить внимание — это:

UI:=Etxt.UserInfo2[listUsers.Items.Objects[listUsers.ItemIndex] as TUser];

Сделать такое стало возможным, т.к. при выводе списка пользователей мы делали так (см. обработчик события OnClick кнопки btnFind выше):

lbUsers.Items.AddObject(Etxt.User[i].Fio, Etxt.User[i]);

Опять же, в зависимости от потребностей и собственных предпочтений, теоретически, в список можно было бы писать не только объекты типа TUser, поэтому в рабочем приложении не лишним будет проверить, что мы получаем из списка именно объект типа TUser. Но в этом приложении-примере я точно знаю, что в списке могут быть только TUser и ничего другого.
Вот теперь наше приложение полностью готово. Можем снова его запустить и полюбоваться конечным результатом:
etxt_api11
Надеюсь, что на данном этапе вы уже освоились с API, более или менее разобрались как формировать запросы к серверу и разбирать его ответы. Теперь можно перейти к другим моментам работы с API онлайн-сервисов, на которые стоит обращать внимание.

Работа с исключениями API

В этой части я не в коем случае не хочу затрагивать тему обработки исключений в Delphi вообще. Во-первых, я не пишу здесь учебник по Delphi для начинающих. А, во-вторых, на эту тему есть замечательная и очень большая статья в «Королевстве Delphi» под названием «Обработка ошибок«. Почитайте — лишним явно не будет. Здесь же я затрону только те моменты, которые касаются непосредственно работы с API.

Очень важный момент работы с любым API — это правильная обработка исключений. А точнее их интерпретация как для себя (разработчика), так и для пользователей. Внимательно изучите документацию API на предмет того как сервер сообщает вам об исключениях.

При работе с API онлайн-сервисов возможны следующие варианты отправки исключений серверами:

1. Сервер отправляет стандартный код статуса HTTP.

Например, сервер может ответить на очередной запрос «404 Not Found»  

В этом случае, всё, что вы можете сделать, чтобы интерпретировать такое исключение — это обратиться к списку кодов состояния HTTP и узнать, что именно сообщил вам сервер. Или же, если у API есть подробная документация, то описание каждого кода статуса дается в этой документации.

Например, API Dropbox на код статуса 404 дает следующее описание исключения — «File or folder not found at the specified path»

Получив такой ответ от сервера Dropbox, можно понять, что в запросе указан неверный путь (иначе сервер ответил бы кодом 200 с пустым JSON-объектом).

2. Сервер всегда отвечает на запрос сообщением с кодом статуса «200 Ok», а информацию об исключении пересылает в теле сообщения

Пример такого сервиса рассмотрен выше. Сервер etxt.ru, как бы я не издевался над ним, пока писал примеры, всегда отвечал кодом статуса 200, а в теле сообщения возвращал или описание ошибки (в виде обычной строки) или JSON-объект с данными. Поэтому во всех методах где шло обращение к серверу, я делал такую простую проверку:

AJson:=GetJson(AResponse);
if not Assigned(AJson) then
  raise Exception.Create(FLastError);

Не смогли получить JSON-объект, значит ответ сервера — это строка с описанием исключения, которую мы и показываем пользователю приложения. Других вариантов тут нет, т.к. если мы дошли до этой проверки, то никаких непредвиденных ситуаций в работе с HTTP не было (иначе исключение «выскочило» бы ещё в момент выполнения метода GET).

В этом случае, сервер выдает вполне понятное и внятное описание исключения, например, «Не верно указан пароль доступа к API» или «Подпись запроса неверна» и т.д.

Стоит отметить, что при таком способе выдачи исключений сервер может выдавать описание ошибки и в виде JSON-объекта. В этом случае, обработка исключения была бы чуть по-сложнее, например, я бы проверял ЧТО содержится в JSON:

//первая пара с именем "error"
if (AJSON.Count>0) and SameText(AJSON.Pairs[0].JsonString.Value, 'error') then
//тут разбираем JSON-объект, находим описание исключения и выдаем пользователю
  raise EMyAPIException.Create('Описание_исключения')

Описание JSON-объекта с исключением обязательно должно даваться в документации к API.

3. Сервер отправляет сообщение с разными кодами статуса (401, 403, 404 и т.д.) и дополняет свое сообщение расширенным описанием исключения.

Такой способ передачи исключений используется в Google API. К примеру, сервер, возвращает код статуса 404 и дополняет сообщение подробным описанием того, что пошло не так. Как получить описание такого исключения? Окончательный код обработки подобных исключений, конечно же, напрямую зависит от того, какую библиотеку для работы с HTTP Вы используете. Например, если используется Indy, то для получения описания исключения можно использовать такой код:

var HTTP:TidHTTP;
    Response: string;
    ErrorText: string;
begin
 try
   Response:=HTTP.Get('http://example.com/api') 
 except
   //сервер код исключения (3хх, 4хх или 5хх)
   on E: EIdHTTPProtocolException do
     begin
        //получаем описание исключения
        ErrorText:=E.ErrorMessage;
        raise EMyAPIException.Create(ErrorText)
     end
    else
      raise;
    end;
end;

Это опять же только пример того как получить описание исключения API в Indy и конечный код обработки зависит от возможностей конкретного API: один сервис вернет простую строку, второй — json-объект, а третий может и XML выслать. Кроме того, не обязательно на любой код исключения будет дано описание в ErrorMessage. Например, сервер может дополнять сообщение об исключении подробным описанием только для кодов статуса 4хх, а 5хх возвращать без описания (опять же сталкивался с таким подходом в некоторых API Google).

Вот, пожалуй все те варианты выдачи исключений серверами с которыми я когда-либо сталкивался на практике. Может есть и другие способы, но я о них не знаю.

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

Заключение

На протяжении всего представленного материала по работе с API онлайн-сервисов в Delphi я старался как можно более подробно рассмотреть каждый шаг — давать самые подробнейшие комментарии в коде (иногда эти комментарии занимали места больше, чем сам код). Искренне надеюсь, что делал я это не зря.

И пусть вас не смущает тот момент, что я рассматривал всего один API, несмотря на то, что материал рассчитан на то, что, изучив его, вы сможете приступить к работе над любым API любого онлайн-сервиса. Разработав более десятка компонентов для работы с API онлайн-сервисов, среди которых и такие популярные как Google Calendar API, Contacts API, Twitter API, Dropbox API, ВКонтакте API, OneDrive API и прочие, я могу с уверенностью сказать — принципы работы с любым API абсолютно те же самые, что и в рассматриваемых примерах:

  1. Изучили введение к API — определились с набором компонентов
  2. Изучили часть документации по авторизации — написали свой метод авторизации, проверили его на простом запросе.
  3. Изучили основную часть — определились с классами и объектами в Delphi
  4. Изучили документацию по исключениям API — написали свои обработчики, если это необходимо

А то, насколько быстро и правильно напишете код Delphi напрямую зависит от того насколько вы хорошо усвоили информацию о которой я говорил в первой части и как внимательно вы прочитали документацию.

 

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

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