【问题标题】:Reliable method for checking that an online file exists检查在线文件是否存在的可靠方法
【发布时间】:2021-03-25 09:21:12
【问题描述】:

我有以下代码检查网站上是否存在文件。有时它有效,有时则无效。当它不起作用时,它会调用 EIdHTTPProtocolException 并返回 0,即使该文件存在于服务器上。有谁知道为什么它并不总是有效?如果我在调试代码,它似乎更有可能工作,所以我想知道它是否与时间有关?

这是一个在 Windows 上运行的 FMX 应用程序,我使用的是 Delphi 10.4。

请注意,我已将链接更改为假链接,以便在此处发布,因此很明显,如果您尝试,下面的代码将始终返回 0。

uses IdHTTP, IdStack;

function CheckFileOnlineExists(const OnlineFile: string; var Size: Int64): Byte;
var
 IdHttp: TIdHTTP;
begin
  Result := 0; // File not found
  IdHttp := TIdHTTP.Create(nil);
try
  try
    IdHttp.Head(OnlineFile);
    Size := IdHttp.Response.ContentLength;
    if Size > 0 then Result := 2; // File found
  except
    on E: EIdHTTPProtocolException do ;
    on E: EIdSocketError do Result := 1; // No internet
  end;
finally
  IdHttp.Free;
end;
end;

procedure TMainForm.FormActivate(Sender: TObject);
Var
  LSize : Int64;
  LRes : Byte;
begin
  // Check if online file exists
  LRes := CheckFileOnlineExists('http://websiteurl.com/file_test.png', LSize);
  if (LRes <> 2) or (LSize <> 5497) then begin
    if LRes = 0 then
      Caption := 'Website check file not found. This app will close'
    else
      Caption := 'Internet connection is needed to run this application';
    Close;
  end;
end;

【问题讨论】:

  • 我建议你捕获流量(例如使用 WireShark),看看它什么时候不起作用,至少 HTTP 协议有什么问题。也许服务器在文件完全发送之前就放弃了连接?
  • @fpiette 这是一个 HEAD 请求,它不发送文件,只是一些关于它的信息。这是非常短的通信,因此预计不会(经常)失败。
  • 也许我应该使用一个 while 循环来继续检查,直到它成功或达到 10 之类的最大计数。
  • 如果文件不存在,服务器应该给你一个404,它是有效的文件存在大小为零。至于为什么会出现协议错误,我建议您遵循@fpiette 的建议并检查实际的http 对话以了解发生了什么。
  • 就像@RobLambden 写道:您还需要检查HTTP status:200 是一个强指标,但404 和410 也是。您甚至无法确定 301 和 307 是否会自动跟随新的 URI,更不用说如果没有给出该状态会发生什么。内容长度也不是自动成为您期望的“文件”之一 - 它也可能是自定义 404 页面的大小。

标签: delphi firemonkey


【解决方案1】:

默认情况下,TIdHTTP 收到来自 HTTP 服务器的错误回复时会引发 EIdHTTPProtocolException。异常的ErrorCode 属性将包含服务器的数字错误代码。在这种情况下,404 对应于 not found。你应该专门处理这种情况。

此外,正如 cmets 中的其他内容所述,远程文件可以存在并且大小为 0 字节。所以你不应该使用ContentLength 作为存在的标志。如果TIdHTTP.Head()退出而没有报错,则文件存在,句号。

试试这个:

function CheckFileOnlineExists(const OnlineFile: string; var Size: Int64): Byte;
var
  IdHttp: TIdHTTP;
begin
  try
    IdHttp := TIdHTTP.Create(nil);
    try
      // TODO: set IdHttp.Request.Accept to tell the server which
      // type of file you will accept and nothing else...
      IdHttp.Head(OnlineFile);
      // TODO: verify the Response.ContentType before assuming success,
      // in case the server sends a 200 OK reply containing an HTML
      // login form or error webpage...
      Size := IdHttp.Response.ContentLength;
      Result := 2; // File found
    finally
      IdHttp.Free;
    end;
  except
    on E: EIdHTTPProtocolException do begin
      Result := iif(E.ErrorCode = 404, 0, 3); // 0: File not found; 3: HTTP error
    end;
    on E: EIdSocketError do Result := 1; // No internet
    on E: Exception do Result := 4; // Unknown error
  end;
end;

procedure TMainForm.FormActivate(Sender: TObject);
var
  LSize : Int64;
  LRes : Byte;
begin
  // Check if online file exists
  LRes := CheckFileOnlineExists('http://websiteurl.com/file_test.png', LSize);
  case LRes of
    0: begin
      Caption := 'Website file not found. This app will close';
    end;
    1: begin
      Caption := 'Internet connection is needed to run this application';
    end;
    2: begin
      if LSize = 5497 then Exit;
      Caption := 'File is not the expected size. This app will close';
    end;
    3: begin
      Caption := 'Website file error. This app will close';
    end;
  else
    begin
      Caption := 'Unknown error. This app will close';
    end;
  end;
  Close;
end;

话虽如此,您可以通过以下任一方式避免EIdHTTPProtocolException 因常见错误而引发的开销:

  • TIdHTTP.HTTPOptions 属性中启用hoNoProtocolErrorException 标志:
function CheckFileOnlineExists(const OnlineFile: string; var Size: Int64): Byte;
var
 IdHttp: TIdHTTP;
begin
  try
    IdHttp := TIdHTTP.Create(nil);
    try
      IdHTTP.HTTPOptions := IdHTTP.HTTPOptions + [hoNoProtocolErrorException];
      // TODO: see above...
      IdHttp.Head(OnlineFile);
      if (IdHttp.ResponseCode div 100) = 2 then
      begin
        // TODO: see above...
        Size := IdHttp.Response.ContentLength;
        Result := 2; // File found
      end
      else if IdHttp.ResponseCode = 404 then
      begin
        Result := 0; // File not found
      end
      else begin
        Result := 3; // HTTP error
      end;
    finally
      IdHttp.Free;
    end;
  except
    on E: EIdSocketError do Result := 1; // No internet
    on E: Exception do Result := 4; // Unknown error
  end;
end;
  • 404(和您感兴趣的任何其他错误代码)传递给TIdHTTP.DoRequest()AIgnoreReplies 参数:
type
  TIdHTTPAccess = class(TIdHTTP)
  end;

function CheckFileOnlineExists(const OnlineFile: string; var Size: Int64): Byte;
var
 IdHttp: TIdHTTP;
begin
  try
    IdHttp := TIdHTTP.Create(nil);
    try
      // TODO: see above ...
      TIdHTTPAccess(IdHttp).DoRequest('HEAD', OnlineFile, nil, nil, [404]);
      Result := IdHttp.ResponseCode <> 404;
      if Result then
      begin
        // TODO: see above ...
        Size := IdHttp.Response.ContentLength;
      end;
    finally
      IdHttp.Free;
    end;
  except
    on E: EIdHTTPProtocolException do begin
      Result := 3; // some other HTTP error
    end;
    on E: EIdSocketError do Result := 1; // No internet
    on E: Exception do Result := 4; // Unknown error
  end;
end;

【讨论】:

  • 谢谢。但是,该文件位于我的 Web 服务器上,并且已知其大小不为零。
  • 没关系。检查 0 的大小不是检查文件是否存在的正确方法。 HTTP 响应代码会告诉您这一点。如果文件存在,您的 OnActivate 处理程序将单独验证大小,让它完成它的工作,不要在 CheckFileOnlineExists() 本身内部进行验证。这不是OnActivate 要求CheckFileOnlineExists() 的目的。
  • case 部分中的else 已经开始一个块(就像try 部分中的finallyexcept)并且不需要冗余的@987654346 @ 表示多个表达式。
  • @AmigoJack 我知道这一点,但我从不喜欢省略begin..end 会使代码看起来如何,所以我更喜欢明确使用begin..end
猜你喜欢
  • 1970-01-01
  • 2020-12-31
  • 2016-11-01
  • 1970-01-01
  • 2015-08-15
  • 2016-11-01
  • 2015-02-21
  • 1970-01-01
  • 2011-01-16
相关资源
最近更新 更多