如何通过 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 服务器检索文件? [复制]