Reliable method for checking that an online file exists

Viewed 328

I have the following code which checks that a file exists on a website. Sometimes it works and sometimes it doesn't. When it doesn't work it invokes EIdHTTPProtocolException and returns 0 even though the file is present on the server. Does anyone have an idea for why it doesn't always work? It seems more likely to work if I'm debugging the code, so I wonder if it has to do with timing?

This is an FMX application running on Windows and I'm using Delphi 10.4.

Note that I have changed the link to a fake one for posting here so obviously the code below will always return 0 if you try it.

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;
1 Answers

By default, EIdHTTPProtocolException is raised when TIdHTTP receives an error reply from the HTTP server. The exception's ErrorCode property will contain the server's numeric error code. In this case, 404 for not found. You should be handling that case specifically.

Also, as others stated in comments, a remote file can exist and be 0 bytes in size. So you should not be using the ContentLength as an indication of existence. If TIdHTTP.Head() exits without reporting an error, the file exists, period.

Try this instead:

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;

That being said, you can avoid the overhead of EIdHTTPProtocolException being raised on commonly-handled errors by either:

  • enabling the hoNoProtocolErrorException flag in the TIdHTTP.HTTPOptions property:
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;
  • passing 404 (and any other error code you are interested in) to the AIgnoreReplies parameter of TIdHTTP.DoRequest():
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;
Related