如何通过 HTTP 从 Internet 检索文件?

Posted

技术标签:

【中文标题】如何通过 HTTP 从 Internet 检索文件?【英文标题】:How to retrieve a file from Internet via HTTP? 【发布时间】:2010-06-26 01:01:08 【问题描述】:

我想从 Internet 下载文件,而 InternetReadFile 乍一看似乎是一个很好且简单的解决方案。事实上,好得令人难以置信。确实,挖掘了一下我已经开始看到它实际上存在很多问题。人们在使用此代码时抱怨各种问题。

出现问题的原因可能是:

应用程序会暂时冻结,直到 HTTP 服务器响应 由于 Internet 连接中断,应用程序暂时冻结 应用程序锁定,因为 HTTP 服务器从不响应 InternetOpen(我最近才发现)在应用程序生命周期内只能调用一次

我找不到关于如何正确和稳健地使用它的完整示例。有没有人知道如何在一个单独的线程中实现它并超时?还有另一种简单的方法可以从 Internet 可靠地下载文件。虽然我不想让绝地甚至印地这样的大型图书馆使我的生活变得复杂。

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;

编辑: 此函数检查 Internet 连接是否可用。它似乎也适用于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 版本 x.x。

将东西安装到用户的计算机中就像玩枪一样。会适得其反。

(在此处查看更多信息:http://thesunstroke.blogspot.com/2010/06/programmig-like-there-is-no-ms-windows.html

【问题讨论】:

这是一个有用的问题,请将下载和/或文件下载添加到标签(连同 delphi 标签)以及 wininet 和 indy(如果适用) 这个问题涉及到:***.com/questions/3135003/… 【参考方案1】:

我基本上和你做的一样。对我来说,它工作得相当完美。

我的代码和您的代码之间的唯一区别是我有一个 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 ...

【讨论】:

对我来说它工作得相当完美——只有在服务器没有问题的情况下。尝试下载一个大文件 (1MB) 并拔下网线。你会看到的。 好吧,我所有的用户都成功地在我的程序中使用它来获取一个 4.5 MB 的文件。我猜他们都没有在下载时拔掉网线。但请确保在尝试下载之前检查互联网连接。我已将此添加到我的答案中。 拔掉电缆会模拟各种问题,例如 ISP 关闭或 Web 服务器关闭。它仍然是一个有效的测试。我已经尝试过了,它冻结了应用程序 :) 另请参阅我的“检查 Internet 是否可用”功能。它似乎也适用于Win98。【参考方案2】:

这里有一些使用 Indy 的示例代码。此代码适用于 Delphi 2010(使用 Indy 10?),但 Delphi 7 的代码类似。我已经在 D7 上使用 Indy 多年,并且对它非常满意。我认为在 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 

【讨论】:

嗨。我的代码依赖于 IE。你知道你的代码有没有依赖吗? @Altar:不太可能(阅读:不可能)遇到缺少 Internet Explorer 系统文件的 Microsoft Windows 系统。 "读取:不可能" - 你错了!有很多没有IE的Win OS。尤其是在欧洲。此外,对“Internet Explorer 卸载”进行简单的 Google 搜索会返回近 400 万条结果。我猜有超过 400 万用户阅读了有关如何卸载 IE 的网站/论坛。 @Altar:虽然 IE 应用程序本身可能不存在,但这并不意味着部分或全部底层引擎不存在。它是操作系统的基本组成部分,应用程序依赖于它。如果用户真正删除了底层内容的所有痕迹,包括 WinInet.dll(InternetReadFile 所依赖的)和 urlmon.dll(TDownloadURL 使用的),那么软件几乎肯定会失败。甚至 Google Chrome 也会加载这些 DLL,both “在 Windows 7 中,Microsoft 添加了从 Windows 中安全删除 Internet Explorer 8 的功能。[99] Microsoft 不允许通过此过程删除依赖项,但 Internet Explorer 可执行文件 (iexplore. exe) 被删除而不会损害任何其他 Windows 组件” - 看起来你是对的。似乎 MS 保证了这些库的存在。【参考方案3】:

我个人最喜欢的是通过导入“Microsoft WinHTTP Services”类型库来使用 WebHttpRequest 组件: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;

【讨论】:

你好 Stijn。它可以处理服务器超时吗? 另外,WinHTTP 是否在所有 Windows 操作系统上都可用,或者我需要显示“此应用程序将无法运行,直到您从 MS 网站下载并安装 x 东西”之类的公告(因为我永远不会这样做)。 w.Send超时会抛出异常,还有一个SetTimeouts方法msdn.microsoft.com/en-us/library/aa384061(v=VS.85).aspx【参考方案4】:

我推荐Synapse。它小巧、稳定且易于使用(无需任何外部库)。

来自 httpsend.pas 的示例

函数 HttpGetText(常量 URL:字符串;常量响应:TStrings):布尔值; 变量 HTTP:THTTPS发送; 开始 HTTP := THTTPSend.创建; 尝试 结果 := HTTP.HTTPMethod('GET', URL); 如果结果那么 Response.LoadFromStream(HTTP.Document); 最后 HTTP.免费; 结尾; 结尾;

【讨论】:

问题仍然存在:它是否支持超时?或者它会冻结应用程序?谢谢 引用:“Synapse 不是一个组件套件,而只是一组类和例程。不需要安装!只需将单元添加到您的 uses 子句中。” - 这就是我需要的。同时,看看这个库的更新率。每年更新一次或更少!不过我想我会试试的。 是的,它支持超时。然而,在单独的线程中组织下载仍然是一个好主意。 我使用这个库已经 4 年多了。从来没有任何问题。无论如何,它是开源的。来源很容易理解和追踪。没有像 Indy 那样复杂的交叉依赖。如果需要,您可以自定义任何内容。【参考方案5】:

ExtActns 单元无需摆弄 WinAPI,而是提供下载到文件所需的内容。

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; 

在底层,它使用来自 URLMon 库的URLDownloadToFile - 这是 IE 的一部分,因此也是 Windows 的一部分。

TDownloadURL 不会为您处理任何超时 - URLMon 似乎根本不支持这样的事情,尽管可能有一些默认超时导致调用失败 - 但您可以使用 OnProgress 事件在 TDownloadURL 上,以便在发生某些事情时得到通知,然后如果距离上次回调的时间过长,则在另一个线程中执行某些操作。

【讨论】:

我的印象是 OP 想要在较低级别控制它,而不是更高级别... 他想要的东西在单独的线程中使用简单,同时仍然很健壮,但不必深入研究 Indy 或 JEDI。这不必排除更高级别的代码,只要更高级别的代码按预期工作即可。 “他想要在单独的线程中简单使用同时仍然很健壮的东西” - 对。虽然“多线程”将是最好的解决方案,但只要代码实现超时,它就不是强制性的。【参考方案6】:

使用上述代码的改进版本解决。 (它仍然没有解决所有问题 - MS 实际上并没有实现对服务器超时的完全支持)

The connection does not timeout while downloading file from internet

【讨论】:

以上是关于如何通过 HTTP 从 Internet 检索文件?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Java 直接从 Internet 读取文本文件?

如何使用 libcurl(或其他方式)从 HTTP 服务器检索文件? [复制]

通过curl和php检索大量数据

如何通过 SFTP 从服务器检索文件?

如何将文件接收地址AS2 URL中的HTTP修改为HTTPS?

从 Internet 流式传输单个音频文件