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

HTTP Client API — это относительно новый фреймворк, появившейся впервые в Delphi XE8 и, получивший развитие в более поздних версиях Delphi. HTTP Client API позволяет вашим приложениям Delphi отправлять запросы к серверам по протоколу HTTP и работать с ответами. В состав фреймворка входят два компонента: TNetHTTPClient и TNetHTTPRequest, а также классы и интерфейсы для работы по HTTP-протоколу.

Этот пример не использует компоненты, однако использует класс THTTPClient для скачивания файла из Сети с использованием HTTP Client API. 

Основная информация по компоненту
Исходник официального примера и документация SourceForge DocWiki

Это приложение использует HTTP Client API для загрузки файла. Загружаемый файл разделяется на 4 части и приложение использует несколько потоков для одновременной загрузки частей.

Приложение использует следующие элементы управления:

  • EditFileName: позволяет указать имя файла.
  • EditURL: позволяет указать URL-адрес файла.
  • BStartDownload: запускает обработчик событий BStartDownloadClick.
  • LabelGlobalSpeed: отображает среднюю скорость загрузки.
  • ImageList1: содержит изображения кнопки «стоп».
  • Button1, Button2, Button3, Button4: запуск обработчика событий ButtonCancelClick.
  • ProgressBarPart1, ProgressBarPart2, ProgressBarPart3, ProgressBarPart4: отображение хода загрузки соответствующей части файла.
  • Label1, Label2, Label3, Label4: отображение скорости загрузки соответствующей части файла.
  • Memo1: отображает сообщения из приложения для пользователя.

Внешний вид демонстрационного приложения представлен на рисунке ниже:

Демонстрационный пример

Клик по кнопке «Start Download» запускает анонимный поток внутри которого работает единственный метод:

procedure TFormDownloadDemo.BStartDownloadClick(Sender: TObject);
begin
  ...
  TThread.CreateAnonymousThread(procedure
  begin
    SampleDownload;
  end).Start;
end;

Процедура SampleDownload выглядит следующим образом:

procedure TFormDownloadDemo.SampleDownload;
var
  LClient: THTTPClient;
  URL: string;
  LResponse: IHTTPResponse;
  StFile: TFileStream;
  LFileName: string;
  LStart, LEnd, LSize, LFragSize: Int64;
  I: Integer;
  LDownloadThreads: array of TDownloadThread;
  LFinished: Boolean;
  LStartTime, LEndTime: Cardinal;
begin
  LClient := THTTPClient.Create;
  LFileName := TPath.Combine(TPath.GetDocumentsPath, EditFileName.Text);
 
  TThread.Synchronize(nil, procedure
  begin
    Memo1.Lines.Add('File location = ' + LFileName);
    Memo1.Lines.Add('Downloading ' + URL + ' ...');
    Application.ProcessMessages;
  end);
  try
    URL := EditURL.Text;
    //Проверяем поддерживает ли сервер возобновление загрузки
    if LClient.CheckDownloadResume(URL) then
    begin
      //запрашиваем с сервера заголовки (Headers). 
      //В заголовках должен вернуться размер загружаемого файла 
      LResponse := LClient.Head(URL);
 
      //Читаем размер файла, который необходимо скачать
      LSize := LResponse.ContentLength;
      //выделяем место под файл
      StFile := TFileStream.Create(LFileName, fmCreate);
      try
        STFile.Size := LSize;
      finally
        STFile.Free;
      end;
 
      // Делим весь размер файла на 4 части
      //NumThreads = 4
      LFragSize := LSize div NumThreads;
      //указываем с какого места в потоке вести запись данных и до какого места
      LStart := 0;
      LEnd := LStart + LFragSize;
      //создаем массив потоков
      //каждый поток будет качать свою часть файла
      SetLength(LDownloadThreads, NumThreads);
      for I := 0 to NumThreads - 1 do
      begin
        if FClosingForm then
          Break;
 
        // Создаем новый поток
        LDownloadThreads[I] := TDownloadThread.Create(URL, LFileName, I, LStart, LEnd);
        LDownloadThreads[I].OnThreadData := ReceiveThreadDataEvent;
 
        TThread.Synchronize(nil, procedure
        begin
          // Настраиваем максимальное значение у ProgressBar'a
          if LEnd >= LSize then
          begin
            ProgressBarArray[I].Max := LFragSize - (LEnd - LSize);
            LEnd := LSize;
          end
          else
            ProgressBarArray[I].Max := LFragSize;
          //обнуляем значения ProgressBar'а
          ProgressBarArray[I].Min := 0;
          ProgressBarArray[I].Value := 0;
 
          ButtonCancelArray[I].Enabled := True;
          LabelProgressArray[I].Text := '0 KB/s';
        end);
 
        // Обновляем значения LStart и LEnd для следующего потока
        LStart := LStart + LFragSize;
        LEnd := LStart + LFragSize;
      end;
 
      // Начинаем загрузку частей файла
      LStartTime := TThread.GetTickCount;
      for I := 0 to NumThreads - 1 do
        LDownloadThreads[I].Start;
 
      // Ждем пока потоки не отработают
      LFinished := False;
      while not LFinished and not FClosingForm do
      begin
        LFinished := True;
        for I := 0 to NumThreads - 1 do
          LFinished := LFinished and LDownloadThreads[I].Finished;
      end;
 
      //показываем пользователю среднюю скорость загрузки
      LEndTime := TThread.GetTickCount - LStartTime;
      TThread.Synchronize(nil, procedure
      begin
        LabelGlobalSpeed.Text := Format('Global Speed: %d KB/s', [((LSize*1000) div LEndTime) div 1024]);
      end);
 
      // Очищаем потоки
      for I := 0 to NumThreads - 1 do
        LDownloadThreads[I].Free;
    end
    else
    begin //сервер не поддерживает возобновление загрузки
      TThread.Synchronize(nil, procedure
      begin
        Memo1.Lines.Add('Server has NOT resume download feature');
      end);
    end;
  finally
    LClient.Free;
    TThread.Synchronize(nil, procedure
    begin
     BStartDownload.Enabled := True;
    end);
    FAllowFormClose := True;
  end;
end;

Таким образом, SampleDownload инициирует загрузку файла по частям, в то время, как процесс «общения» с сервером осуществляется в отдельных потоках TDownloadThread.
TDownloadThread имеет следующее описание:

type
  TDownloadThreadDataEvent = procedure(const Sender: TObject; ThreadNo, ASpeed: Integer; AContentLength: Int64; AReadCount: Int64; var Abort: Boolean) of object;
  TDownloadThread = class(TThread)
  private
    FOnThreadData: TDownloadThreadDataEvent;
 
  protected
    FURL, FFileName: string;
    FStartPoint, FEndPoint: Int64;
    FThreadNo: Integer;
    FTimeStart: Cardinal;
 
    procedure ReceiveDataEvent(const Sender: TObject; AContentLength: Int64; AReadCount: Int64; var Abort: Boolean);
  public
    constructor Create(const URL, FileName: string; ThreadNo: Integer; StartPoint, EndPoint: Int64);
    destructor Destroy; override;
    procedure Execute; override;
 
    property OnThreadData: TDownloadThreadDataEvent write FOnThreadData;
  end;

Метод Execute потока выглядит следующим образом:

procedure TDownloadThread.Execute;
var
  LResponse: IHTTPResponse;
  LStream: TFileStream;
  LHttpClient: THTTPClient;
begin
  inherited;
  //создаем объект клиента HTTP
  LHttpClient := THTTPClient.Create;
  try
    //настраиваем обработчик события OnReceiveData
    //событие срабатывает при получении очередного ответа от сервера
    LHttpClient.OnReceiveData := ReceiveDataEvent;
    //настраиваем поток для хранения данных
    LStream := TFileStream.Create(FFileName, fmOpenWrite or fmShareDenyNone);
    try
      FTimeStart := GetTickCount;
      LStream.Seek(FStartPoint, TSeekOrigin.soBeginning);
      //отправляем запрос на получение части файла
      LResponse := LHttpClient.GetRange(FURL, FStartPoint, FEndPoint, LStream);
    finally
      LStream.Free;
    end;
  finally
    LHttpClient.Free;
  end;
end;

Обработчик OnReceiveData выглядит следующим образом:

procedure TDownloadThread.ReceiveDataEvent(const Sender: TObject; AContentLength, AReadCount: Int64;
  var Abort: Boolean);
var
  LTime: Cardinal;
  LSpeed: Integer;
begin
  if Assigned(FOnThreadData) then
  begin
    LTime := GetTickCount - FTimeStart;
    if AReadCount = 0 then
      LSpeed := 0
    else
      LSpeed := (AReadCount * 1000) div LTime;
    //отправляем событие потока OnThreadData
    //передаем номер потока, скорость загрузки, размер скачанных данных
    FOnThreadData(Sender, FThreadNo, LSpeed, AContentLength, AReadCount, Abort);
  end;
end;

Этот демонстрационный пример показывает как загрузить файл с помощью HTTP Client API и при этом использует синхронный режим. В этом случае, для того, чтобы интерфейс приложения не «подвисал» используются отдельные потоки для работы с каждой частью файла. Стоит отметить, что HTTP Client API поддерживает также и асинхронный режим работы.
На рисунке ниже представлен пример работающего демонстрационного приложения:

Смотрите также

При подготовке статьи использовалась информация со следующих ресурсов:

  1. Официальный репозиторий демонстрационных примеров Delphi на SourceForge
  2. Информация по работе с классом THTTPClient на сайте Embarcadero

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