Как получить файл из Интернета через HTTP? - PullRequest
4 голосов
/ 26 июня 2010

Я хочу скачать файл из Интернета, а InternetReadFile на первый взгляд кажется хорошим и простым решением.На самом деле, слишком хорошо, чтобы быть правдой.Действительно, копая немного, я начал понимать, что на самом деле есть много проблем с этим.Люди жалуются на всевозможные проблемы при использовании этого кода.

Могут возникнуть проблемы из-за:

  • приложение временно зависает до тех пор, пока HTTP-сервер не ответит
  • приложение временно зависает из-за разрыва соединения с Интернетом
  • приложение блокируется, потому что HTTP-сервер никогда не отвечает
  • InternetOpen (я только что обнаружил это недавно) ДОЛЖЕН вызываться только один раз за время жизни приложения

Я не смог найти полный примеро том, как использовать это правильно и надежно.Кто-нибудь есть идеи о том, как реализовать это в отдельном потоке и с тайм-аутом?Существует еще один простой способ надежно загрузить файл из Интернета.Хотя я не хочу усложнять свою жизнь такими большими библиотеками, как Jedi или даже Indy.

function GetFileHTTP (const fileURL, FileName: String): boolean;
CONST
  BufferSize = 1024;
VAR
  hSession, hURL: HInternet;
  Buffer: array[1..BufferSize] of Byte;
  BufferLen: DWORD;
  f: File;
  sAppName: string;
begin
//  result := false;
 sAppName := ExtractFileName(Application.ExeName) ;
 hSession := InternetOpen(PChar(sAppName), INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0) ;  { be aware that InternetOpen  need only be called once in your application!!!!!!!!!!!!!! }
 TRY
  hURL := InternetOpenURL(hSession, PChar(fileURL), nil, 0, 0, 0) ;
  TRY
   AssignFile(f, FileName) ;
   Rewrite(f, 1) ;
   REPEAT
    InternetReadFile(hURL, @Buffer, SizeOf(Buffer), BufferLen);
    BlockWrite(f, Buffer, BufferLen)
   UNTIL BufferLen = 0;
   CloseFile(f) ;
   Result:= True;
  FINALLY
   InternetCloseHandle(hURL)
  end
 FINALLY
  InternetCloseHandle(hSession)
 END;
END;

Редактировать: Эта функция проверяет, доступно ли подключение к Интернету.Кажется, он работает и на Win98.

{  Are we connected to the Internet? }
function IsConnectedToInternet: Boolean;                                        { Call SHELL32.DLL for Win < Win98 otherwise call URL.dll }
var InetIsOffline: function(dwFlags: DWORD): BOOL; stdcall;
begin
 Result:= FALSE;
 if IsApiFunctionAvailable('URL.DLL', 'InetIsOffline', @InetIsOffline)
 then Result:= NOT InetIsOffLine(0)
 else
   if IsApiFunctionAvailable('SHELL32.DLL', 'InetIsOffline', @InetIsOffline)
   then Result:= NOT InetIsOffLine(0)
end;

Я использую Delphi 7. Большое спасибо.


Редактировать:

Потеря клиентов из-за зависания приложения наПервый запуск - это идеальный рецепт для потери денег.

Писать код, зависящий от платформы Microsoft, плохо.Вы никогда не знаете, установлена ​​ли у клиента версия IE xx.

Установка чего-либо на компьютер пользователя - это все равно что играть с оружием.Это будет иметь неприятные последствия.

(подробнее об этом здесь: http://thesunstroke.blogspot.com/2010/06/programmig-like-there-is-no-ms-windows.html)

Ответы [ 6 ]

4 голосов
/ 26 июня 2010

Я делаю то же самое, что и вы.Для меня это работает довольно безупречно.

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

  hURL := InternetOpenURL(hSession, PChar(fileURL), nil, 0, INTERNET_FLAG_RELOAD, 0) ; 

Также проверьте подключение к Интернету перед загрузкой.Сделайте это:

  dwConnectionTypes := INTERNET_CONNECTION_MODEM
                 + INTERNET_CONNECTION_LAN
                 + INTERNET_CONNECTION_PROXY;
  InternetConnected := InternetGetConnectedState(@dwConnectionTypes, 0);
  if InternetConnected then ...
2 голосов
/ 26 июня 2010

Вот пример кода, который использует Indy. Этот код предназначен для Delphi 2010 (с Indy 10?), Но код для Delphi 7 будет аналогичным. Я годами использовал Indy с D7 и был очень доволен этим. Я думаю, что в D7 мы используем Indy 9. Проверьте, нужно ли вам загружать новую версию ...

Вы можете использовать OnWork и OnWorkBegin, чтобы добавить индикатор прогресса, если вам нужно.

Этот код я взял из большого фрагмента, немного его редактируя. Я не пытался скомпилировать его, но он даст вам хорошее начальное место.

function Download( const aSourceURL: String;
                   const aDestFileName: String;
                   out   aDownloadResult: TDownloadResult;
                   out   aErrm: String): boolean;
var
  Stream: TMemoryStream;
  IDAntiFreeze: TIDAntiFreeze;
begin
  aDownloadResult := DROther;
  Result := FALSE;
  fIDHTTP := TIDHTTP.Create;
  fIDHTTP.HandleRedirects := TRUE;
  fIDHTTP.AllowCookies := FALSE;
  fIDHTTP.Request.UserAgent := 'Mozilla/4.0';
  fIDHTTP.Request.Connection := 'Keep-Alive';
  fIDHTTP.Request.ProxyConnection := 'Keep-Alive';
  fIDHTTP.Request.CacheControl := 'no-cache';
  IDAntiFreeze := TIDAntiFreeze.Create;

  Stream := TMemoryStream.Create;
  try
    try
      fIDHTTP.Get(aSourceURL, Stream);
      if FileExists(aDestFileName) then
        DeleteFile(PWideChar(aDestFileName));
      Stream.SaveToFile(aDestFileName);
      Result := TRUE;
      aDownloadResult :=drSuccess;
    except
      On E: Exception do
        begin
          Result := FALSE;
          aErrm := E.Message + ' (' + IntToStr(fIDHTTP.ResponseCode) + ')';
        end;
    end;
  finally
    Stream.Free;
    IDAntiFreeze.Free;
    fIDHTTP.Free;
  end;
end;  { Download }
1 голос
/ 02 июля 2010

Я рекомендую Synapse . Это небольшой, стабильный и простой в использовании (не требует никаких внешних библиотек).

Пример из httpsend.pas

function HttpGetText(const URL: string; const Response: TStrings): Boolean;
var
  HTTP: THTTPSend;
begin
  HTTP := THTTPSend.Create;
  try
    Result := HTTP.HTTPMethod('GET', URL);
    if Result then
      Response.LoadFromStream(HTTP.Document);
  finally
    HTTP.Free;
  end;
end;
1 голос
/ 28 июня 2010

Мой личный фаворит - использование компонента WebHttpRequest для импорта библиотеки типов «Microsoft WinHTTP Services»: http://yoy.be/item.asp?i142

var
  w:IWebHttpRequest;
  f:TFileStream;  
  os:TOleStream;
begin 
  w:=CoWebHttpRequest.Create;
  w.Open('GET',SourceURL,false);
  w.Send(EmptyParam);
  os:=TOleStream.Create(IUnknown(w.ResponseStream) as IStream);
  f:=TFileStream.Create(DestinationFilePath,fmCreate);
  os.Position:=0;
  f.CopyFrom(os,os.Size);
  f.Free;
  os.Free;
  w:=nil;
end;
0 голосов
/ 06 июля 2010

Решено с использованием улучшенной версии кода выше.(это все еще не решает все проблемы - MS фактически не реализовала полную поддержку тайм-аута сервера)

Соединение не прерывается при загрузке файла из Интернета

0 голосов
/ 27 июня 2010

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

procedure TMainForm.DownloadFile(URL: string; Dest: string); 
var 
  dl: TDownloadURL; 
begin 
  dl := TDownloadURL.Create(self); 
  try 
    dl.URL := URL; 
    dl.FileName := Dest; 
    dl.ExecuteTarget(nil); //this downloads the file 
    dl.Free; 
  except 
    dl.Free; 
  end; 
end; 

Под капотом он использует URLDownloadToFile из библиотеки URLMon- который является частью IE и, следовательно, частью Windows.

TDownloadURL не обрабатывает тайм-аут для вас - не похоже, что такая вещь вообще поддерживается в URLMon, хотя могут быть некоторыетайм-аут по умолчанию, который вызывает сбой вызова - но вы можете использовать событие OnProgress в TDownloadURL, чтобы получать уведомления, когда что-то происходит, и затем делать что-то в другом потоке, если это было слишком долго с момента последнего обратного вызова.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...