delphiindylazarusfreepascal

How to set ConnectTimeout/ReadTimeout in Indy SSL


How i can set ConnectTimeout/ReadTimeout in Indy when using SSL ?

MCVE:

program mcve;

uses
  {$IFDEF UNIX}{$IFDEF UseCThreads}
  cthreads,
  {$ENDIF}{$ENDIF}SysUtils, IdHTTP, IdSSLOpenSSL, DateUtils;

var
  HTTP    : TIdHTTP;
  SSL     : TIdSSLIOHandlerSocketOpenSSL;
  Started : TDateTime;
begin
  HTTP := TIdHTTP.Create();
  try
    HTTP.ReadTimeout            := 1000;
    HTTP.ConnectTimeout         := 2000;
    SSL                         := TIdSSLIOHandlerSocketOpenSSL.Create(HTTP);
    SSL.ConnectTimeout          := HTTP.ConnectTimeout;
    SSL.ReadTimeout             := HTTP.ReadTimeout;
    SSL.SSLOptions.SSLVersions  := [sslvTLSv1, sslvTLSv1_1, sslvTLSv1_2];
    HTTP.IOHandler              := SSL;
    Started := Now;
    try
      HTTP.Get(ParamStr(1));
    except
      On E: Exception do WriteLn(E.Message);
    end;
    Writeln(FormatDateTime('hh:nn:ss', SecondsBetween(Started, Now) / SecsPerDay));
  finally
    HTTP.Free;
  end;
end.
   

When using http ConnectTimeout/ReadTimeout work fine the issue only when using https see below:

:~$ ./mcve http://x.x.x.x
Read timed out.
00:00:01 <-- Correct.

:~$ ./mcve https://x.x.x.x
Socket Error # 0

00:03:38 <-- NOT Correct / More than SSL.ReadTimeout value.

Lazarus 2.0.6 Indy installed from OPM version 10.6.2.5494.

Note: On Windows same code using Delphi with shipped Indy 10.6.2.5366, The results works as expected

Update:09/06/2025

I wanted to share a quick update about the question. Since it was resolved a while back, as Remy Lebeau mentioned, I know it’s been some time. Yet, I’ve found myself facing the same issue again and went back to the solution, unfortunately making the same mistake as in my comment. It took me quite a bit of time to sort this out the, as the timeval variable is measured in seconds, not milliseconds. here’s the accurate code I wrote for resolving this issue:

procedure TCustomIdHTTP.OnBeforeConnect(ASender: TIdSSLIOHandlerSocketOpenSSL);
var
  time : timeval;
  res  : Integer;
begin
  time.tv_sec  := Round(ASender.ConnectTimeout / 1000);
  time.tv_usec := 0;
  res := fpsetsockopt(ASender.Binding.Handle, Id_SOL_SOCKET, Id_SO_RCVTIMEO, @time, SizeOf(time));
  if res < 0 then
    raise Exception.Create(SysErrorMessage(GetLastOSError));
end;

Solution

  • You don't need to manually set the ConnectTimeout and ReadTimeout on the IOHandler itself, only on the client component (in this case, TIdHTTP) . TIdTCPClient.Connect() will assign the values to the IOHandler for you.

    The ConnectTimeout applies when the underlying socket is being connected to the server, before any SSL/TLS session is created, so it operates the same whether you use SSL/TLS or not.

    The ReadTimeout applies when Indy attempts to read bytes from the IOHandler's internal connection. When not using SSL/TLS, that means it goes straight to the socket, and thus times out when no bytes arrive on the socket. But when using SSL/TLS, Indy uses OpenSSL's legacy SSL_...() APIs, not its newer BIO_...() APIs, which means OpenSSL is doing its own socket reading and buffering on Indy's behalf, and thus Indy times out when OpenSSL does not provide any decrypted application bytes.

    One difference in how TIdSSLIOHandlerSocketOpenSSL operates on Windows vs other platforms is that on Windows Vista+ only, TIdSSLIOHandlerSocketOpenSSL does apply the ReadTimeout to the underlying socket's SO_RCVTIMEO and SO_SNDTIMEO timeouts via the IOHandler's Binding.SetSockOpt() method, as a workaround to an OpenSSL bug on Windows. For other platforms, Indy does not currently set those two socket timeouts.

    A good place to set those timeouts manually would be in the IOHandler's OnBeforeConnect event, which is fired after the socket is connected to the server and before any SSL/TLS session is created.