使用WinInet在下载文件之前确定总文件的大小

问题描述:

我从第三方网站了解如何使用WinInet从互联网下载文件。我对API不太熟悉,然后我看着WinInet单元,但没有看到任何我需要的API调用。使用WinInet在下载文件之前确定总文件的大小

我正在做的是添加报告下载文件进度的能力。这个程序我已经包装在TThread里面,一切正常。但是,只有一个缺失的部分:在下载之前查找源文件的总大小。

看下面我在哪里发表评论//HOW TO GET TOTAL SIZE?这是我需要找出什么是文件的总大小之前,我开始下载它。我如何去做这件事?因为这个代码似乎不知道文件的大小,直到它被完成下载 - 这使得这个添加不相关。

procedure TInetThread.Execute; 
const 
    BufferSize = 1024; 
var 
    hSession, hURL: HInternet; 
    Buffer: array[1..BufferSize] of Byte; 
    BufferLen: DWORD; 
    f: File; 
    S: Bool; 
    D: Integer; 
    T: Integer; 
    procedure DoWork(const Amt: Integer); 
    begin 
    if assigned(FOnWork) then 
     FOnWork(Self, FSource, FDest, Amt, T); 
    end; 
begin 
    S:= False; 
    try 
    try 
     if not DirectoryExists(ExtractFilePath(FDest)) then begin 
     ForceDirectories(ExtractFilePath(FDest)); 
     end; 
     hSession:= InternetOpen(PChar(FAppName), INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0); 
     try 
     hURL:= InternetOpenURL(hSession, PChar(FSource), nil, 0, 0, 0); 
     try 
      AssignFile(f, FDest); 
      Rewrite(f, 1); 
      T:= 0; //HOW TO GET TOTAL SIZE? 
      D:= 0; 
      DoWork(D); 
      repeat 
      InternetReadFile(hURL, @Buffer, SizeOf(Buffer), BufferLen); 
      BlockWrite(f, Buffer, BufferLen); 
      D:= D + BufferLen; 
      DoWork(D); 
      until BufferLen = 0; 
      CloseFile(f); 
      S:= True; 
     finally 
      InternetCloseHandle(hURL); 
     end 
     finally 
     InternetCloseHandle(hSession); 
     end; 
    except 
     on e: exception do begin 
     S:= False; 
     end; 
    end; 
    finally 
    if assigned(FOnComplete) then 
     FOnComplete(Self, FSource, FDest, S); 
    end; 
end; 
+3

我实现了这样一个功能,发现使用的WinInet导致可怕的“超时错误”在我的应用程序发生。通常需要100毫秒的Http头请求需要15秒才能返回。在某些版本的Windows/WinInet上,从Delphi调用WinInet是已知的问题。如果你以后遇到这种奇怪的故障,我会提到这一点。如果你可以在这里使用Indy或者非WinInet(例如WinHttp),请考虑它! :-) – 2012-02-06 20:16:07

+0

'..这是在Delphi的某些版本的Windows/WinInet上从Delphi调用WinInet时出现的一个已知问题@WarrenP我从来没有使用Delphi的WinInet来解决这个问题。你能指出一些关于这个话题的文件或链接吗? – RRUZ 2012-02-06 20:30:55

+0

下面是一个链接:http://jgobserve.blogspot.com/2009/03/wininet-timeout-issue-and-solution.html - 我的观察结果是,问题并不局限于这样一个事实,即当底层网络发生故障,你会长时间等待。有时候一切都很好,除了winInet有超时之外,我无法解释。我使用Python或Delphi编写的使用INDY或ICS编写的代码不会显示相同的故障模式。 – 2012-02-06 20:39:25

您可以使用HEAD方法和检查Content-Length检索一个远程文件的文件大小

检查这两种方法

的WinInet

如果你想执行HEAD方法您必须使用HttpOpenRequest,HttpSendRequestHttpQueryInfo WinInet函数。

uses 
SysUtils, 
Windows, 
WinInet; 

function GetWinInetError(ErrorCode:Cardinal): string; 
const 
    winetdll = 'wininet.dll'; 
var 
    Len: Integer; 
    Buffer: PChar; 
begin 
    Len := FormatMessage(
    FORMAT_MESSAGE_FROM_HMODULE or FORMAT_MESSAGE_FROM_SYSTEM or 
    FORMAT_MESSAGE_ALLOCATE_BUFFER or FORMAT_MESSAGE_IGNORE_INSERTS or FORMAT_MESSAGE_ARGUMENT_ARRAY, 
    Pointer(GetModuleHandle(winetdll)), ErrorCode, 0, @Buffer, SizeOf(Buffer), nil); 
    try 
    while (Len > 0) and {$IFDEF UNICODE}(CharInSet(Buffer[Len - 1], [#0..#32, '.'])) {$ELSE}(Buffer[Len - 1] in [#0..#32, '.']) {$ENDIF} do Dec(Len); 
    SetString(Result, Buffer, Len); 
    finally 
    LocalFree(HLOCAL(Buffer)); 
    end; 
end; 


procedure ParseURL(const lpszUrl: string; var Host, Resource: string); 
var 
    lpszScheme  : array[0..INTERNET_MAX_SCHEME_LENGTH - 1] of Char; 
    lpszHostName : array[0..INTERNET_MAX_HOST_NAME_LENGTH - 1] of Char; 
    lpszUserName : array[0..INTERNET_MAX_USER_NAME_LENGTH - 1] of Char; 
    lpszPassword : array[0..INTERNET_MAX_PASSWORD_LENGTH - 1] of Char; 
    lpszUrlPath  : array[0..INTERNET_MAX_PATH_LENGTH - 1] of Char; 
    lpszExtraInfo : array[0..1024 - 1] of Char; 
    lpUrlComponents : TURLComponents; 
begin 
    ZeroMemory(@lpszScheme, SizeOf(lpszScheme)); 
    ZeroMemory(@lpszHostName, SizeOf(lpszHostName)); 
    ZeroMemory(@lpszUserName, SizeOf(lpszUserName)); 
    ZeroMemory(@lpszPassword, SizeOf(lpszPassword)); 
    ZeroMemory(@lpszUrlPath, SizeOf(lpszUrlPath)); 
    ZeroMemory(@lpszExtraInfo, SizeOf(lpszExtraInfo)); 
    ZeroMemory(@lpUrlComponents, SizeOf(TURLComponents)); 

    lpUrlComponents.dwStructSize  := SizeOf(TURLComponents); 
    lpUrlComponents.lpszScheme  := lpszScheme; 
    lpUrlComponents.dwSchemeLength := SizeOf(lpszScheme); 
    lpUrlComponents.lpszHostName  := lpszHostName; 
    lpUrlComponents.dwHostNameLength := SizeOf(lpszHostName); 
    lpUrlComponents.lpszUserName  := lpszUserName; 
    lpUrlComponents.dwUserNameLength := SizeOf(lpszUserName); 
    lpUrlComponents.lpszPassword  := lpszPassword; 
    lpUrlComponents.dwPasswordLength := SizeOf(lpszPassword); 
    lpUrlComponents.lpszUrlPath  := lpszUrlPath; 
    lpUrlComponents.dwUrlPathLength := SizeOf(lpszUrlPath); 
    lpUrlComponents.lpszExtraInfo  := lpszExtraInfo; 
    lpUrlComponents.dwExtraInfoLength := SizeOf(lpszExtraInfo); 

    InternetCrackUrl(PChar(lpszUrl), Length(lpszUrl), ICU_DECODE or ICU_ESCAPE, lpUrlComponents); 

    Host := lpszHostName; 
    Resource := lpszUrlPath; 
end; 

function GetRemoteFileSize(const Url : string): Integer; 
const 
    sUserAgent = 'Mozilla/5.001 (windows; U; NT4.0; en-US; rv:1.0) Gecko/25250101'; 

var 
    hInet : HINTERNET; 
    hConnect : HINTERNET; 
    hRequest : HINTERNET; 
    lpdwBufferLength: DWORD; 
    lpdwReserved : DWORD; 
    ServerName: string; 
    Resource: string; 
    ErrorCode : Cardinal; 
begin 
    ParseURL(Url,ServerName,Resource); 
    Result:=0; 

    hInet := InternetOpen(PChar(sUserAgent), INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0); 
    if hInet=nil then 
    begin 
    ErrorCode:=GetLastError; 
    raise Exception.Create(Format('InternetOpen Error %d Description %s',[ErrorCode,GetWinInetError(ErrorCode)])); 
    end; 

    try 
    hConnect := InternetConnect(hInet, PChar(ServerName), INTERNET_DEFAULT_HTTP_PORT, nil, nil, INTERNET_SERVICE_HTTP, 0, 0); 
    if hConnect=nil then 
    begin 
     ErrorCode:=GetLastError; 
     raise Exception.Create(Format('InternetConnect Error %d Description %s',[ErrorCode,GetWinInetError(ErrorCode)])); 
    end; 

    try 
     hRequest := HttpOpenRequest(hConnect, PChar('HEAD'), PChar(Resource), nil, nil, nil, 0, 0); 
     if hRequest<>nil then 
     begin 
      try 
      lpdwBufferLength:=SizeOf(Result); 
      lpdwReserved :=0; 
      if not HttpSendRequest(hRequest, nil, 0, nil, 0) then 
      begin 
       ErrorCode:=GetLastError; 
       raise Exception.Create(Format('HttpOpenRequest Error %d Description %s',[ErrorCode,GetWinInetError(ErrorCode)])); 
      end; 

      if not HttpQueryInfo(hRequest, HTTP_QUERY_CONTENT_LENGTH or HTTP_QUERY_FLAG_NUMBER, @Result, lpdwBufferLength, lpdwReserved) then 
      begin 
       Result:=0; 
       ErrorCode:=GetLastError; 
       raise Exception.Create(Format('HttpQueryInfo Error %d Description %s',[ErrorCode,GetWinInetError(ErrorCode)])); 
      end; 
      finally 
      InternetCloseHandle(hRequest); 
      end; 
     end 
     else 
     begin 
      ErrorCode:=GetLastError; 
      raise Exception.Create(Format('HttpOpenRequest Error %d Description %s',[ErrorCode,GetWinInetError(ErrorCode)])); 
     end; 
    finally 
     InternetCloseHandle(hConnect); 
    end; 
    finally 
    InternetCloseHandle(hInet); 
    end; 

end; 

利用Indy还要检查这些代码。

function GetRemoteFilesize(const Url :string) : Integer; 
var 
    Http: TIdHTTP; 
begin 
    Http := TIdHTTP.Create(nil); 
    try 
    Http.Head(Url); 
    result:= Http.Response.ContentLength; 
    finally 
    Http.Free; 
    end; 
end; 
+2

+1需要注意的是,我应该使用Indy代替:D仅仅为了干净的代码 – 2012-02-06 20:52:23

+3

如果您知道*您将要下载资源,您是否可以发送GET请求并从Content-Length头中读取Content-Length标头而不是?它会为你节省一个额外的HTTP连接。 – 2012-02-06 21:16:08

+6

@RobKennedy - 是的,只要数据不是使用“Transfer-Encoding:chunked”头部以块形式发送的,在这种情况下,不会使用“Content-Length”头部,并且没有办法知道直到最后一个块被接收的总大小。 – 2012-02-06 22:04:30

回答如何使用WinInet下载大小的问题。这是基于WinInet的文件下载器之一。

这是我用得到的下载大小的方法:

function TWebDownloader.GetContentLength(URLHandle: HINTERNET): Int64; 
// returns the expected download size. Returns -1 if one not provided 
    var 
    SBuffer: Array[1..20] of char; 
    SBufferSize: Integer; 
    srv: integer; 
    begin 
    srv := 0; 
    SBufferSize := 20; 
    if HttpQueryInfo(URLHandle, HTTP_QUERY_CONTENT_LENGTH, @SBuffer, SBufferSize, srv) then 
     Result := StrToFloat(String(SBuffer)) 
    else 
     Result := -1; 
    end; 

使用这种方法需要一个开放的要求处理,并且不需要读取任何数据:

URLHandle := HttpOpenRequest(ConnectHandle, 'GET', Pchar(sitepath), nil, 
        nil, nil, INTERNET_FLAG_NO_CACHE_WRITE, 0); 
... 
DownloadSize := GetContentLength(URLHandle); 

HTH

+0

+1好东西,和1/6的代码作为其他答案:)顺便说一句,你的巨大下载项目是如何来的? – 2012-02-06 22:36:50

+0

我真的好奇它是如何工作的,因为条件是互斥的a)方法是GET b)没有请求资源的传输。我猜测它会在收到标题时关闭连接。 – OnTheFly 2012-02-07 00:05:33

+0

@JerryDodge它已经足够满足我的需求,我转向其他事情。但它仍然需要大量的清理工作。 – Glenn1234 2012-03-03 02:47:32

固定后类型看起来好像这样:

function GetContentLength(URLHandle:HINTERNET):Int64; 
// returns the expected download size. Returns -1 if one not provided 
var 
SBufferSize, srv:Cardinal; 
begin 
srv:=0; 
SBufferSize:=20; 
if Not HttpQueryInfo(URLHandle, HTTP_QUERY_CONTENT_LENGTH or HTTP_QUERY_FLAG_NUMBER, {@SBuffer} @Result, SBufferSize, srv) then Result:=-1; 
end; 

叫它:

{get the file handle} 
hURL:=InternetOpenURL(hSession, PChar(URL), nil, 0, 0, 0); 
if hURL=Nil then 
begin 
InternetCloseHandle(hSession); 
ShowMessage('The link is incorrect!'); 
exit; 
end; 
{get the file size} 
filesize:=GetContentLength(hURL);