TIdHTTPServer Contexts Count keeps increasing - delphi

I have created a TIdHTTPServer based web server that runs on Windows. Indy version is very recent (Jan 2015). I have MaxConnections set to 1000, and have developed a mechanism to restart the web server when DoMaxConnections is called.
Once an hour I log the connection count:
TIdThreadSafeList(IdHTTPServer1.Contexts).count;
For the most part the connection count is always increasing. Once in a while I will see it decrease (say from 525 to 520). But the overall trend is increasing. After 5 days or so it gets to 1000 and the server resets (I understand 1000 is relatively small, but don't think it is germane to the subject).
Even on weekends, when traffic is lower, it still increases. I would have thought it would decrease at times of lower traffic. Why are some of these connections not closing?
I do netstat -an and see a lot of TIME_WAIT status.
Other relevant source code:
ServerIOHandler := TIdServerIOHandlerSSLOpenSSL.Create(self);
ServerIOHandler.SSLOptions.CertFile := 'C:\xxxxxx.crt';
ServerIOHandler.SSLOptions.KeyFile := 'C:\xxxxxx.key';
ServerIOHandler.SSLOptions.RootCertFile := 'C:=xxxxxx.crt';
ServerIOHandler.SSLOptions.Method := sslvSSLv23;
ServerIOHandler.SSLOptions.Mode := sslmServer;
IdHTTPServer1 := TIdHTTPServer.Create;
IdHTTPServer1.MaxConnections := 1000;
IdHTTPServer1.AutoStartSession := True;
IdHTTPServer1.KeepAlive := True;
IdHTTPServer1.SessionState := True;
IdHTTPServer1.OnCommandGet := MainGet;
idHttpServer1.ParseParams := True;
idHttpServer1.IOHandler := ServerIOHandler;
idHttpServer1.Bindings.Add.Port := 80;
idHttpServer1.Bindings.Add.Port := 443;
IdHTTPServer1.Active := True;
Update I thought it would be useful to add some netstat statistics.
With the server running for 24+ Hours:
Contexts Count: 587
netstat ESTABLISHED from external to port 80:580
netstat TIME_WAIT from external to port 80:819
I then restarted my Indy Service (did not reboot windows server however):
Contexts Count: 60
netstat ESTABLISHED from external to port 80:49
netstat TIME_WAIT from external to port 80:797
Update 2 - Request was made to show the MainGet procedure. There is too much code to feasibly do this. But what I have done is to provide snippets of code that could shed light on problem. Below is a list of some of the calls I am making (under different circumstances to improve performance).
AResponseInfo.ContentText := filetostring('c:\.....');
AResponseInfo.ResponseNo
Aresponseinfo.RawHeaders.Add('Connection:close');
Aresponseinfo.CustomHeaders.Add('Keep-Alive: timeout=30');
Aresponseinfo.Connection:='keep-alive';
AResponseInfo.ContentStream := MemoryStream;
AResponseInfo.Redirect('xxxxx');
AResponseInfo.ServeFile(AContext,mFile);
AResponseinfo.CacheControl:='max-age=1209600';
Update 3 - provide additional information of my the MainGet function. I am keeping track of the number of times the function is called and the number of times that it exits. The value of iGlobalGetStart and iGlobalGetFinish are always very close (certainly way less than number of contexts that are active).
procedure TMyWebUnit.MainGet(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
try
InterlockedIncrement(iGlobalGetStart);
.... all of my logic
finally
InterlockedIncrement(iGlobalGetFinish);
end;
Update 4 - I have implemented additional tracking code. I have created my own class descended from TIdServerContext. In it I set the date/time it is created, and also have a date/time member called DatetimeLastUsed which will get updated in oncommandget. Here is the class:
type
TEAIdServerContext = class(TIdServerContext)
DateTimelastUsed,DateTimeCreated: TDateTime;
constructor Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TIdContextThreadList = nil); override;
destructor Destroy; override;
end;
I set the IdHTTPServer1.ContextClass to this new class:
IdHTTPServer1.ContextClass := TEAIdServerContext;
In my OnCommandGet, the first thing I do is to set the date/time of DateTimeLastUsed to the current time:
TEAIDServerContext(AContext).DateTimeLastUsed := Now;
I also have a function which will list all contexts:
procedure SayContexts();
var
i: Integer;
mstring: String;
mList: TList;
begin
mList := IdHTTPServer1.Contexts.LockList;
mstring := 'Contexts:'+inttostr(mlist.Count);
with mList do try
for i := 0 to count -1 do begin
mString := mString +
datetimetostr(TEAIDServerContext(mList[i]).datetimecreated)+'; last used:'+datetimetostr(TEAIDServerContext(mList[i]).datetimeLastUsed)
end;
end;
IdHTTPServer1.Contexts.UnLockList;
....
end;
The connections that are remaining open are never getting to the oncommandget code because their datetimelastused field is null. Is this normal?

Related

Delphi RIO - Indy TCPServer high CPU usage

I have a simple TCP file server program developed in Delphi RIO + Indy TCP Server. When 2 or more clients asks for the files, the CPU runs very high in 90s. This spooks off the server team and during this time, they have hard time login into the server on which the program is running.
Based on other threads on the subject, when I put IndySleep(x), it does bring the CPU down and the avg stays in 50-60s. I understand that putting IndySleep() may throttle a bit, but it works!
The files it serves are already compressed and vary in size from 1KB to <10MB.
Is there anything else I can do to improve overall CPU usage, without or with little IndySleep()?
Here is the code snippet:
procedure TMainForm.IdTCPSyncServerExecute(AContext: TIdContext);
begin
if (not AContext.Connection.IOHandler.InputBufferIsEmpty)
and (AContext.Connection.Connected) then
begin
SendFile(AContext, AContext.Connection.IOHandler.ReadLn);
//IndySleep(1000 div IdTCPSyncServer.Contexts.Count); // For high CPU
IndySleep(500); // For high CPU
end;
end;
procedure TMainForm.SendFile(AContext: TIdContext; AFileName: string);
var
lStream: TFileStream;
begin
lStream := TFileStream.Create(AFileName, fmOpenRead or fmShareDenyWrite);
if Assigned(lStream) then
begin
try
WriteRespHeader(AContext, 1, lStream.Size); //Custom fn() writes back to client file size and other useful info
AContext.Connection.IOHandler.LargeStream := False; // 32-bit
lStream.Position := 0;
AContext.Connection.IOHandler.Write(lStream, lStream.Size);
finally
lStream.Free;
end;
AddLogMsg(AContext.Binding.PeerIP + ' : Sent File: ' + AFileName); // Thread.Queue() based logging
end;
end;
You have the call to IndySleep() in the wrong place. If there is nothing available from the client to read yet, you are exiting OnExecute immediately and coming right back in, creating a tight loop. That is where your high CPU usage is likely occurring. Sleep only when there is nothing available yet, eg:
procedure TMainForm.IdTCPSyncServerExecute(AContext: TIdContext);
begin
if (not AContext.Connection.IOHandler.InputBufferIsEmpty)
and (AContext.Connection.Connected) then
begin
SendFile(AContext, AContext.Connection.IOHandler.ReadLn);
end else begin
//IndySleep(1000 div IdTCPSyncServer.Contexts.Count); // For high CPU
IndySleep(500); // For high CPU
// or, use AContext.Connection.IOHandler.Readable() instead...
// or, use AContext.Connection.IOHandler.CheckForDataOnSoure() instead...
end;
end;
Alternatively, I usually suggest this kind of manual check instead:
procedure TMainForm.IdTCPSyncServerExecute(AContext: TIdContext);
begin
if AContext.Connection.IOHandler.InputBufferIsEmpty then
begin
AContext.Connection.IOHandler.CheckForDataOnSource(500{1000 div IdTCPSyncServer.Contexts.Count}); // For high CPU
AContext.Connection.IOHandler.CheckForDisconnect;
if AContext.Connection.IOHandler.InputBufferIsEmpty then Exit;
end;
SendFile(AContext, AContext.Connection.IOHandler.ReadLn);
end;
But really, in this case, a better solution is to simply not manually check for the presence of client data at all. If there is nothing available to read yet, just let IOHandler.ReadLn() block until something actually arrives, eg:
procedure TMainForm.IdTCPSyncServerExecute(AContext: TIdContext);
begin
SendFile(AContext, AContext.Connection.IOHandler.ReadLn);
end;

Non overlapped Serial Port hangs at CloseHandle

I wrote a serial port class that I developed and for simplicity I used blocking/synchronous/non-overlapped. I went through all MSDN documentations and it was strait forward for me.
I don't have any problem with Opening, Transmitting or Receiving Bytes from the port. All operations are synchronous and there is no-threading complexity.
function TSerialPort.Open: Boolean;
var
h: THandle;
port_timeouts: TCommTimeouts;
dcb: TDCB;
begin
Result := False;
if Assigned(FHandleStream) then
begin
// already open
Exit(True);
end;
h := CreateFile(PChar('\\?\' + FComPort),
GENERIC_WRITE or GENERIC_READ, 0, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
// RaiseLastOSError();
if h <> INVALID_HANDLE_VALUE then
begin
{
REMARKS at https://learn.microsoft.com/en-us/windows/desktop/api/winbase/ns-winbase-_commtimeouts
If an application sets ReadIntervalTimeout and ReadTotalTimeoutMultiplier to MAXDWORD and
sets ReadTotalTimeoutConstant to a value greater than zero and less than MAXDWORD, one
of the following occurs when the ReadFile function is called:
* If there are any bytes in the input buffer, ReadFile returns immediately with the bytes in the buffer.
* If there are no bytes in the input buffer, ReadFile waits until a byte arrives and then returns immediately.
* If no bytes arrive within the time specified by ReadTotalTimeoutConstant, ReadFile times out.
}
FillChar(port_timeouts, Sizeof(port_timeouts), 0);
port_timeouts.ReadIntervalTimeout := MAXDWORD;
port_timeouts.ReadTotalTimeoutMultiplier := MAXDWORD;
port_timeouts.ReadTotalTimeoutConstant := 50; // in ms
port_timeouts.WriteTotalTimeoutConstant := 2000; // in ms
if SetCommTimeOuts(h, port_timeouts) then
begin
FillChar(dcb, Sizeof(dcb), 0);
dcb.DCBlength := sizeof(dcb);
if GetCommState(h, dcb) then
begin
dcb.BaudRate := FBaudRate; // baud rate
dcb.ByteSize := StrToIntDef(FFrameType.Chars[0], 8); // data size
dcb.StopBits := ONESTOPBIT; // 1 stop bit
dcb.Parity := NOPARITY;
case FFrameType.ToUpper.Chars[1] of
'E': dcb.Parity := EVENPARITY;
'O': dcb.Parity := ODDPARITY;
end;
dcb.Flags := dcb_Binary or dcb_Parity or dcb_ErrorChar or
(DTR_CONTROL_ENABLE shl 4) or (RTS_CONTROL_ENABLE shl 12);
dcb.ErrorChar := '?'; // parity error will be replaced with this char
if SetCommState(h, dcb) then
begin
FHandleStream := THandleStream.Create(h);
Result := True;
end;
end;
end;
if not Result then
begin
CloseHandle(h);
end;
end;
end;
function TSerialPort.Transmit(const s: TBytes): Boolean;
var
len: NativeInt;
begin
Result := False;
len := Length(s);
if Assigned(FHandleStream) and (len > 0) then
begin
// total timeout to transmit is 2sec!!
Result := (FHandleStream.Write(s, Length(s)) = len);
end;
end;
function TSerialPort.Receive(var r: Byte): Boolean;
begin
Result := False;
if Assigned(FHandleStream) then
begin
// read timeout is 50ms
Result := (FHandleStream.Read(r, 1) = 1);
end;
end;
My problem starts at closing the port.
After all my communications, when I try to close the serial port, my Application totally hangs at CloseHandle() API. And that happens randomly. Which is meaningless to me since I use synchronous mode, there can not be any pending operations. When I request a close, It must simply close the handle.
I searched the problem on the google and stack-overflow. There are many people who faced the similar problems but most of them are related with .NET serial port driver and their asynchronous mode operations which I don't have.
And also some people forgot to set timeouts properly and they faced blocking issue at ReadFile and WriteFile API that is fully normal. But again this is not my problem, I've set CommTimeouts as it is indicated in MSDN remarks.
function TSerialPort.Close: Boolean;
var
h: THandle;
begin
Result := True;
if Assigned(FHandleStream) then
begin
h := FHandleStream.Handle;
FreeAndNil(FHandleStream);
if h <> INVALID_HANDLE_VALUE then
begin
//PurgeComm(h, PURGE_TXABORT or PURGE_RXABORT or PURGE_TXCLEAR or PURGE_RXCLEAR); // didn't help
//ClearCommError(h, PDWORD(nil)^, nil); // didn't help
//CancelIO(h); // didn't help
Result := CloseHandle(h); <------------ hangs here
end;
end;
end;
Some people on Microsoft forum, suggest calling CloseHandle() in different thread. I have tried that as well. But that time it hangs while trying to free AnonymousThread that I created. Even I left FreeOnTerminate:=true as default, it hangs and I get memory leakage report by Delphi.
Another bothering problem when it hangs, I have to close Delphi IDE fully and reopen. Otherwise I can't compile the code again since exe is still used.
function TSerialPort.Close: Boolean;
var
h: THandle;
t: TThread;
Event: TEvent;
begin
Result := True;
if Assigned(FHandleStream) then
begin
h := FHandleStream.Handle;
FreeAndNil(FHandleStream);
if h <> INVALID_HANDLE_VALUE then
begin
PurgeComm(h, PURGE_TXABORT or PURGE_RXABORT or PURGE_TXCLEAR or PURGE_RXCLEAR);
Event := TEvent.Create(nil, False, False, 'COM PORT CLOSE');
t := TThread.CreateAnonymousThread(
procedure()
begin
CloseHandle(h);
If Assigned(Event) then Event.SetEvent();
end);
t.FreeOnTerminate := False;
t.Start;
Event.WaitFor(1000);
FreeAndNil(t); // <---------- that time it hangs here, why??!!
FreeAndNil(Event);
end;
end;
end;
In my notebook I'm using USB to Serial Port converters from FTDI. Some people said that it is because of FTDI driver. But I'm using all microsoft drivers that is signed by Microsoft Windows Hardware Compatibility Publisher. There is no third party driver in my system. But when I disconnect the USB adapter, CloseHandle API unfreeze itself. Some people reports that, even native Serial Ports that are build in their motherboards have same issue.
So far I couldn't solve the problem. Any help or workaround highly appreciated.
Thanks.
This issue is with the FTDI USB-Serial converter driver. It doesn't handle the hardware flow control properly and on occasion will hang in CloseHandle call.
To get around the issue, implement hardware flow control manually. In C++ (not sure how it would be done in Delphi) set up these DCB structure fields in order to allow manual control of the RTS line:
// Assuming these variables are defined in the header
HANDLE m_hComm; // Comm port handle.
DCB m_dcb; // DCB comm port settings.
// Put these settings in the DCB structure.
m_dcb.fRtsControl = RTS_CONTROL_ENABLE;
m_dcb.fOutxCtsFlow = TRUE;
Then use
EscapeCommFunction(m_hComm, CLRRTS); // Call this before calling WriteFile.
And
EscapeCommFunction(m_hComm, SETRTS); // Call this after Write is complete.
In your case, because its synchronous - you can just wrap every call to WriteFile with these 2 calls. If using asynchronous (like in my case), call the one with SETRTS after you get the completion event from the ovelapped structure in your WriteFile call.
Used to freeze all the time before we implemented this as we were using 12 serial ports, and only way to unlock the port would be restarting the computer.
Now works like a charm with manual control, hasn't frozen once since.
One thing to keep in mind, some USB-Serial devices (or even different versions of FTDI) may invert the RTS line! So if the above doesn't work, try using SETRTS to set the line low and CLRRTS to set it high.
Edit: If you have access to a Windows XP machine, use portmon tool to see what is happening with the RTS line, this way you will know if it is inverted or not or if it is getting the commands at all.

Why does my Delphi Indy idHTTpServer stop responding when CLOSE_WAIT?

The Environment
I've created a web server in Delphi using Indy component TidHTTPServer. I'm using Delphi XE2 which came with Indy version 10.5.8. The
server is running as a desktop app with a form that displays a log of the connections and their requests. It is running on Windows 7
Professional. Requests are for SQL data from a Firebird database. The response is JSON. All traffic is HTTP.
The Challenge
When I was testing it with a small number of users everything worked great. Now that I have rolled it out to about 400 users there are
communication problems. The server stops responding to requests and the only way I can get it to respond again is to reboot the machine it is running on and then restart it. The need to reboot occurs more frequently during
high volume times.
The Symptoms
Using Windows netstat I have noticed that whenever a TCP connection of type CLOSE_WAIT occurs the server stops responding to requests and I have to reboot again
The Test Procedure
I have been able to simulate this hanging even when there is no traffic on the server. I created a web page that sends multiple requests with
a delay between each request.
The web page let's me specify the number of requests to make, how long to wait between each request, and how long to wait before timing out. Even at one millisecond between requests the server seems to respond without issue.
The Test Results
If I set the time out period of each request to a very small number, like 1 msec, I can make my Delphi HTTP Server hang. At a 1 msec timeout requests to my server fail every time, as I would expect. The time out is so short my server can't possibly respond quickly enough.
What I don't understand is that after I force this timeout at the client side, even a relatively small number of requests (fewer than 50), my Delphi web server no longer responds to any requests. When I run netstat on the server machine there are a number of CLOSE_WAIT socket connections. Even after an hour and after closing my server the CLOSE_WAIT socket connections persist.
The Questions
What is going on? Why does my Delphi Indy idHTTPServer stop responding when there are (even just one) CLOSE_WAIT socket connection? The CLOSE_WAITs don't go away and the server does not start responding again. I have to reboot.
What am I not doing?
Here is the results of netstat command showing CLOSE_WAITs:
C:\Windows\system32>netstat -abn | findstr 62000
TCP 0.0.0.0:62000 0.0.0.0:0 LISTENING
TCP 10.1.1.13:62000 9.49.1.3:57036 TIME_WAIT
TCP 10.1.1.13:62000 9.49.1.3:57162 CLOSE_WAIT
TCP 10.1.1.13:62000 9.49.1.3:57215 CLOSE_WAIT
TCP 10.1.1.13:62000 9.49.1.3:57244 CLOSE_WAIT
TCP 10.1.1.13:62000 9.49.1.3:57263 CLOSE_WAIT
TCP 10.1.1.13:62000 9.49.1.3:57279 ESTABLISHED
TCP 10.1.1.13:62000 104.236.216.73:59051 ESTABLISHED
Here is the essence of my web server:
unit MyWebServer;
interface
Uses
...
Type
TfrmWebServer = class(TForm)
...
IdHTTPServer: TIdHTTPServer;
...
procedure IdHTTPServerCommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
procedure IdHTTPServerDisconnect(AContext: TIdContext);
procedure btnStartClick(Sender: TObject);
...
dbFirebird : TIBDatabase;
txFireird : TIBTransaction;
...
private
function CreateSomeResponseStringData: string;
end;
implementation
procedure TfrmWebServer.btnStartClick(Sender: TObject);
begin
{set the IP's and proit to listen on}
IdHTTPServer.Bindings.Clear;
IdHTTPServer.Bindings.Add.IP := GetSetting(OPTION_TCPIP_ADDRESS);
IdHTTPServer.Bindings.Add.Port := Str2Int(GetSetting(OPTION_TCPIP_PORT));
{start the web server}
IdHTTPServer.Active := TRUE;
...
dbFirebird.Transactrion := txFirebird;
...
end;
procedure TfrmWebServer.IdHTTPServerCommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
qryFirebird : TIBSql;
function CreateSomeResponseStringData: string;
begin
qryFirebird := NIL;
qryFirebird := TIBSql.Create(IdHTTPServer);
qryFirebird.Database := dbFirebird;
dbFirebird.Connected := FALSE;
dbFirebird.Connected := TRUE;
qryFirebird.Active := TRUE;
Result := {...whatever string will be returned}
end;
function CreateAnErrorResponse: string;
begin
Result := {...whatever string will be returned}
end;
begin
try
AResponseInfo.ContentText := CreateSomeResponseStringData;
{Clean up: What do I do here to make sure that the connection that was served is:
- properly closed so that I don't run out of resourses?
- anything that needs to be cleaned up is freed so no memory leaks
- TIME_WAIT, CLOSE_WAIT, any other kind of _WAITs are not accumulating?}
except;
AResponseInfo.ContentText := CreateAnErrorResponse;
end;
qryFirebird.Free;
end;
procedure TfrmWebServer.IdHTTPServerDisconnect(AContext: TIdContext);
begin
{Maybe I do the "Clean Up" here? I tried Disconnect as shown but still lots of
TIME_WAIT tcp/ip connections accumulate. even after the app is closed}
AContext.Connection.Disconnect;
end;
end.
There are at least two major issues with this code that could cause the crashing:
The database and transaction objects are global to all threads created by IdHTTPServer. When you disconnect the database it would disconnect for all threads.
If there is a run time error assigning content text this line AResponseInfo.ContentText := CreateAnErrorResponse; is not in an exception block.
Here is how I would fix this:
...
procedure TfrmWebServer.btnStartClick(Sender: TObject);
begin
{set the IP's and port to listen on}
IdHTTPServer.Bindings.Clear;
IdHTTPServer.Default.Port := Str2Int(GetSetting(OPTION_TCPIP_PORT));
IdHTTPServer.Bindings.Add.IP := GetSetting(OPTION_TCPIP_ADDRESS);
{start the web server}
IdHTTPServer.Active := TRUE;
...
end;
procedure TfrmWebServer.IdHTTPServerCommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
{make these local to each thread}
qryFirebird : TIBSql;
dbFirebird : TIBDatabase;
txFirebird : TIBTransaction;
function CreateSomeResponseStringData: string;
begin
dbFirebird := TIBDatbase.Create(IdHTTPServer);
txFirebird := TIBTransaction.Create(IdHTTPServer);
qryFirebird := TIBSql.Create(IdHTTPServer);
dbFirebird.Transaction := txFirebird;
qryFirebird.Database := dbFirebird;
...Add params that do the log in to database
dbFirebird.Connected := TRUE;
qryFirebird.Active := TRUE;
Result := {...whatever string will be returned}
end;
function CreateAnErrorResponse: string;
begin
Result := {...whatever string will be returned}
end;
begin
try
try
...
AResponseInfo.ContentText := CreateSomeResponseStringData;
...
except;
try
AResponseInfo.ContentText := CreateAnErrorResponse;
except
{give up}
end;
end;
finaly
qryFirebird.Free;
dbFirebird.Free;
txFirebird.Free;
end;
end;
end.

Checking for internet connection in runtime

In which event or how can i check for internet connections while program is running? Scenario: program is running and suddenly internet connection goes off and a dialog opens "No internet access, changing connection string to central database". I tried this function, but im not sure in which event should i put so that it works all the time.
function TFK_Lib.CheckInternet: boolean;
begin
ConnectedState := INTERNET_CONNECTION_MODEM;
Result := InternetGetConnectedState(#ConnectedState, 0);
end;
You can use a timer to check (at a time period) the connection and show the dialog (as a modal so you can't do anything else).
You can do a practical test and ping google.com.
If I understood your question correctly I thinks its not a bad option.
Code example:
class function TAuthUserFunctions.CheckInternet: boolean;
var
idtcp : TIdTCPClient;
begin
try
idtcp := TIdTCPClient.Create(Nil);
try
idtcp.ReadTimeout := 2000;
idtcp.ConnectTimeout := 2000;
idtcp.Port := 80;
idtcp.Host := 'google.com';
idtcp.Connect;
idtcp.Disconnect;
Result := True;
finally
idtcp.Free;
end;
except
Result := False;
end;
end;

Is there a way to set response timeout for Indy Tidhttp gets?

I have built a simple website monitoring application using Indy TIdhttp component. I want to detect when a designated page is not returned within a specified time frame (I am using 5000 milliseconds). As a test I created a page on a web site which intentionally takes 15 seconds to respond. But I can't get my procedure to "give up" after the 5 seconds. I have tried ReadTimeout, a suggested solution using a timer and the OnWorkBegin event (was never able to get OnWorkBegin to fire immediately after the get call).
Note I am not worried about a connection timeout. My concern here is a timeout for the server to return with a page.
Here is some source code I have been using. It contains many of the elements I reference.
procedure TServLogic.WorkBegin(ASender: TObject; AWorkMode: TWorkMode; AWorkCountMax: Int64);
begin
GetTimer.Enabled := True;
end;
procedure TServLogic.WorkEnd(ASender: TObject; AWorkMode: TWorkMode);
begin
GetTimer.Enabled := False;
end;
procedure TServLogic.GetTimerTimer(Sender: TObject);
begin
idHttp.Disconnect(True);
end;
procedure TServLogic.CallHttp(mlink: String): String;
begin
result := '';
GetTimer := TTimer.create(nil);
GetTimer.OnTimer := GetTimerTimer;
GetTimer.Interval := 5000;
try
IdHTTP := TIdHTTP.create(nil);
idhttp.ReadTimeout := 5000;
IdHttp.OnWorkBegin := WorkBegin;
IdHttp.OnWorkEnd := WorkEnd;
try
result := idhttp.get(mLink);
except
on e:exception do begin
AppendToLog('Server did not respond withing 5 seconds');
end;
end;
finally
GetTimer.Free;
idhttp.free;
end;
end;
This doesn't answer the particular question above, but since this is where Google takes you if you search for "indy http timeout", I will mention here that you can set these properties:
TIdHTTP.ReadTimeout
TIdHTTP.ConnectTimeout
I eventually got the answer based on the comments of Rob Kennedy. I tried numerous times to contact him and request he make a "formal" answer so that I could give him the vote. Never heard back.

Resources