Related to a post of mine ( How to retrieve a file from Internet via HTTP? ) about how to easily and robustly download a file from Internet, I have found a possible solution - however is not working as it was supposed to work.
According to MS documentation, the code below is supposed to time-out at 500ms after I disconnect myself from internet. However, it looks like it totally ignores the 'INTERNET_OPTION_RECEIVE_TIMEOUT' setting. The application freezes during download. It takes about 20-30 to this function to realize that there the Internet connection is down and to give the control back to the GUI.
Anybody knows why?
function GetBinFileHTTP (const aUrl: string; const pStream: TStream; wTimeOut: Word= 500; wSleep: Word= 500; wAttempts: Word= 10): Integer;
CONST
BufferSize = 1024;
VAR
hSession, hService: HINTERNET;
Buffer : array[0..BufferSize-1] of Char;
dwBytesRead, dwBytesAvail: DWORD;
lSucc : LongBool;
lRetries, dwTimeOut: Integer;
begin
Result:= 0;
if NOT IsConnectedToInternet then
begin
Result:= -1;
EXIT;
end;
hSession := InternetOpen(PChar(ExtractFileName(Application.ExeName)), INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0); { The INTERNET_OPEN_TYPE_PRECONFIG flag specifies that if the user has configured Internet Explorer to use a proxy server, WinInet will use it as well. }
if NOT Assigned(hSession) then
begin
Result:= -4;
EXIT;
end;
TRY
hService := InternetOpenUrl(hSession, PChar(aUrl), nil, 0, INTERNET_FLAG_RELOAD, 0);
if NOT Assigned(hService) then Exit;
TRY
FillChar(Buffer, SizeOf(Buffer), 0);
{ Set time out }
dwTimeOut:= wTimeOut;
InternetSetOption(hService, INTERNET_OPTION_RECEIVE_TIMEOUT, #dwTimeOut, SizeOf(dwTimeOut)); { use INTERNET_FLAG_RELOAD instead of NIL to redownload the file instead of using the cache }
InternetSetOption(hService, INTERNET_OPTION_CONNECT_TIMEOUT, #dwTimeOut, SizeOf(dwTimeOut));
REPEAT
lRetries := 0;
REPEAT
lSucc:= InternetQueryDataAvailable( hService, dwBytesAvail, 0, 0);
if NOT lSucc
then Sleep( wSleep );
if lRetries > wAttempts
then Result:= -2;
UNTIL lSucc OR (Result= -2);
if NOT InternetReadFile(hService, #Buffer, BufferSize, dwBytesRead) then
begin
Result:= -3; { Error: File not found/File cannot be downloaded }
EXIT;
end;
if dwBytesRead = 0
then Break;
pStream.WriteBuffer(Buffer[0], dwBytesRead);
UNTIL False;
FINALLY
InternetCloseHandle(hService);
end;
FINALLY
InternetCloseHandle(hSession);
end;
Result:= 1;
end;
Here is the documentation:
{
INTERNET_OPTION_CONNECT_TIMEOUT Sets or retrieves an unsigned long integer value that contains the time-out value to use for Internet connection requests. If a connection request takes longer than this time-out value, the request is canceled. When attempting to connect to multiple IP addresses for a single host (a multihome host), the timeout limit is cumulative for all of the IP addresses. This option can be used on any HINTERNET handle, including a NULL handle. It is used by InternetQueryOption and InternetSetOption.
INTERNET_OPTION_RECEIVE_TIMEOUT Sets or retrieves an unsigned long integer value that contains the time-out value to receive a response to a request. If the response takes longer than this time-out value, the request is canceled. This option can be used on any HINTERNET handle, including a NULL handle. It is used by InternetQueryOption and InternetSetOption. For using WinInet synchronously, only the default value for this flag can be changed by calling InternetSetOption and passing NULL in the hInternet parameter.
INTERNET_OPTION_CONTROL_RECEIVE_TIMEOUT - Identical to INTERNET_OPTION_RECEIVE_TIMEOUT. This is used by InternetQueryOption and InternetSetOption.
}
Edit:
I disconnect the Internet by unplugging the cable or (for wireless) from software AFTER the application starts the download (I chose to download large file). It simulates the web site going offline.
The connect timeout obviously isn't applicable in your test because by the time you start your test (i.e., pull the plug), the connection has already been established. Indeed, the connection is already established before you even get around to setting the timeout option.
The validity of the receive timeout is also suspect, because you've already begun receiving the response, too.
The most promising-looking timeout is the disconnect timeout, but MSDN says that's not implemented yet.
It seems to me that the way to go is to use asynchronous operations. Use InternetReadFileEx and use the irf_Async and irf_No_Wait flags. If too much time passes without receiving any data, close the connection. Another option is to stick with your synchronous calls, but then call InternetCloseHandle from another thread if the download takes too long.
There is a documented bug in MS IE code. Can only be solved by using the code in a thread and re-implementing the time out mechanism.
Details:
"This acticle shows a workaround to the InternetSetOption API bug on setting timeout values by creating a second thread.
InternetSetOption Does Not Set Timeout Values"
http://support.microsoft.com/default.aspx?scid=kb;en-us;Q224318
(Link was reported broken. Blame MS not me)
Maybe somebody can help with implementing this bug fix also in Delphi. I personally don't have experience with C. Even the backbone in pseudo-Pascal will be nice.
IMO, you should run this in a thread. Threading does not have to mean looping - it can be a "one and done" thread. Run it that way, and your GUI remains responsive until the thread finishes. I realize that this does not actually answer your question, but it will make your code better.
Also, if you disconnect the internet during the first loop where you're checking for data, I think it will retry 10 times. You should detect, and then quit right away.
Lastly, I don't think you should use EXIT when you've got handles and stuff open. Break instead, so that you still run through the disconnects. I would expect your code to tie up the socket. I saw this recently during a code review when there was an EXIT intead of a BREAK, and it's causing a memory leak because objects are created and never freed. I'd use the same rule here.
Are you sure that you aren't hitting the INTERNET_OPTION_CONNECT_TIMEOUT? It will try to connect first, then receive.
In order to test the connect timeout, it must resolve, but never connect. In order to test the read timeout, it must connect, but never receive any data.
I generally set my connect timeout to 10 seconds, and the read timeout to 30 seconds. Anything longer than that, I consider down anyway.
Related
Is it possible to read the HTTP headers (specifically the GET header) in the Indy TIdHTTPServer.OnConnect event without interfering with the subsequent OnCommandGet event?
If I try to pull them with a loop of ReadLn's then OnCommandGet never fires. I need an advanced peek at them without pulling them from the input buffer.
Is it possible to read the HTTP headers (specifically the GET header) in the Indy TIdHTTPServer.OnConnect event without interfering with the subsequent OnCommandGet event?
It it possible, in that you could use the TIdIOHandler.WaitFor() method to wait for the header terminator to arrive in the TIdIOHandler.InputBuffer, returning everything received before it without removing anything from the buffer, eg:
procedure TMyForm.IdHTTPServer1Connect(AContext: TIdContext);
var
headers: String;
begin
header := AContext.Connection.IOHandler.WaitFor(EOL+EOL, False);
...
end;
However, this has some limitations:
it assumes each line is terminated by the byte sequence $0D $0A and thus the header is terminated by the byte sequence $0D $0A $0D $0A. This is technically true per the HTTP standards, and will usually be the case. However, some clients do terminate lines with just $0A and thus the header would be terminated by $0A $0A. TIdHTTPServer would normally handle that just fine, but using WaitFor() would not.
A more robust solution would involve using TIdIOHandler.CheckForDataOnSource() in a loop, manually scanning the TIdIOHandler.InputBuffer until either $0D $0A $0D $0A or $0A $0A is found in the buffer.
this won't work if there are multiple HTTP requests on the same connection, which can happen if HTTP keep-alives or HTTP pipelining are used. You would be "peeking" the header of only the 1st HTTP request on the connection.
If I try to pull them with a loop of ReadLn's then OnCommandGet never fires.
Correct, because TIdHTTPServer expects to be the one to read them off the InputBuffer. If you read them yourself beforehand, there won't be anything left for TIdHTTPServer to read, so it won't know what each HTTP request even looks like.
I need an advanced peek at them without pulling them from the input buffer.
Why? What do you want to do with them, if you could get them?
You should check if the TIdHTTPServer.OnHeadersAvailable event suits your needs. It is fired at the beginning of every HTTP request, after the headers have been read from the InputBuffer but before the request body has been read.
I got it working by peeking at the Inputbuffer per Remy's suggestion:
procedure TForm1.IdHTTPServer1Connect(AContext: TIdContext);
var
s: string;
Done: boolean;
begin
Done := False;
repeat
Sleep(10);
if AContext.Connection.IOHandler.CheckForDataOnSource then
begin
s := AContext.Connection.IOHandler.InputBuffer.AsString;
if (Pos(#13#10#13#10, s) > 0) or (Pos(#10#10, s) > 0) then Done := True;
end;
until Done;
...
end;
One issue I could see happening is a bot making a TCP connection on my port and that loop never ending since no headers are coming. I would need to add some kind of timeout check.
The other suggestion of using OnHeadersAvailable wouldn't work for me because it's called right before OnCommandGet each time (i.e., multiple times per connection when KeepAlive is True) so I might as well just put tests in OnCommandGet if I went that route.
EDIT:
I also just tried doing this in the OnConnect handler:
s := AContext.Connection.IOHandler.WaitFor(#10, False, True, nil, 1000);
Since I only need the GET line and it will always be first (right?) if it's included at all, I just need to find the first line feed character. That solves the line terminator problem and there's a timeout parameter that solves the bot issue. While this does read the first line header, it also causes an immediate disconnect and CommandGet never gets called. What am I doing wrong?
I have an application written in Delphi 6 that uses the Indy 9.0.18 HTTP client component to drive a robot. If I recall correctly when I did the Indy install, version 10 and newer does not work with Delphi 6 so I am using 9.0.18. On newer desktops the program runs completely fine. But I am having some problems on older laptops. Note in all case I am getting absolutely no errors or Exceptions.
The robot is an HTTP server that responds to HTTP requests to drive it. To obtain continuous motion, you have to send a drive command (e.g. - move forward) in a continuous loop. A drive command is an HTTP request to the numeric IP address the robot responds to, no domain name is involved with the URL used in the request so that rules out domain name resolution as a problem (I believe). Once you get the response from the last HTTP request, you turn around immediately and send the next one. You can tell when a system is having trouble keeping up with the loop because the robot makes small jerky moves, never able to reach the continuous momentum needed for smooth motion because the motors have time to settle down and stop.
The two systems that are having trouble are laptop computers and have the following CPU and memory and are running Windows XP SP3:
AMD Turion 64 X2 Mobile technology TL-50 (dual core), 1 GB of main memory, 1.6 GHz, dual core.
AMD Sempron(tm) 140 Processor, 1 GB main memory, 2.7 GHZ. Note, this CPU is a dual core but only one core is enabled.
Both of these systems can not obtain smooth motion except transiently as indicated below.
The reason I say it's a laptop problem is because the two systems above are laptops. In contrast, I have an old Pentium 4 single core with Hyperthreading (2.8 GHz, 2.5 GB of memory). It can obtain smooth motion. However, although continuous the robot moves noticeably slower indicating that there's still a slight delay between the HTTP requests, but not enough to completely stop the motors and therefore the motion is still continuous, albeit noticeably slower than on my quad core or a dual core desktop.
I am aware that the other discriminant in the data points is that the old Pentium 4 desktop has 2.5 times memory over the laptops, despite being a nearly archaic PC. Perhaps the real culprit is some memory thrashing? Every now and then the robot does run smoothly but soon reverts back to stuttering again, indicating that without whatever is mucking up the interaction over the socket, smooth motion is occasionally possible. Note, the robot is also streaming audio both ways to and from the PC and streaming video to the PC (but not the other direction), so there's a fair amount of processing going on along with driving the robot.
The Indy HTTP client is created and runs on a background thread, not on the main Delphi thread, in a tight loop with no sleep states. It does do a PeekMessage() call in the loop to see if any new commands have come in that should be looped instead of the currently looping one. The reason for the GetMessage() call in the loop is so that the thread blocks when the robot is supposed to be idle, that is, no HTTP requests should be sent to it until the user decides to drive it again. In that case, posting a new command to the thread unblocks the GetMessage() call and the new command is looped.
I tried raising the thread priority to THREAD_PRIORITY_TIME_CRITICAL but that had absolutely zero effect. Note I did use GetThreadPriority() to make sure the priority was indeed raised and it returned a value of 15 after initially returning 0 before the SetThreadPriority() call.
1) So what can I do to improve the performance on these older low power systems, since several of my best users have them?
2) The other question I have is does anyone know if Indy has to rebuild the connection each HTTP request or does it cache the socket connection intelligently so that would not be the problem? Would it make a difference if I resorted to using a lower level Indy client socket and did the HTTP request crafting myself? I'd like to avoid that possibility since it would be a significant rewrite, but if that's a high certainty solution, let me know.
I have included the loop for the background thread below in case you can see anything inefficient. Commands to be executed are posted to the thread via an asynchronous PostThreadMessage() operation from the main thread.
// Does the actual post to the robot.
function doPost(
commandName, // The robot command (used for reporting purposes)
// commandString, // The robot command string (used for reporting purposes)
URL, // The URL to POST to.
userName, // The user name to use in authenticating.
password, // The password to use.
strPostData // The string containing the POST data.
: string): string;
var
RBody: TStringStream;
bRaiseException: boolean;
theSubstituteAuthLine: string;
begin
try
RBody := TStringStream.Create(strPostData);
// Custom HTTP request headers.
FIdHTTPClient.Request.CustomHeaders := TIdHeaderList.Create;
try
FIdHTTPClient.Request.Accept := 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8';
FIdHTTPClient.Request.ContentType := 'application/xml';
FIdHTTPClient.Request.ContentEncoding := 'utf-8';
FIdHTTPClient.Request.CacheControl := 'no-cache';
FIdHTTPClient.Request.UserAgent := 'RobotCommand';
FIdHTTPClient.Request.CustomHeaders.Add('Connection: keep-alive');
FIdHTTPClient.Request.CustomHeaders.Add('Keep-Alive: timeout=30, max=3 header');
// Create the correct authorization line for the commands stream server.
theSubstituteAuthLine :=
basicAuthenticationHeaderLine(userName, password);
FIdHTTPClient.Request.CustomHeaders.Add(theSubstituteAuthLine);
Result := FIdHTTPClient.Post(URL, RBody);
// Let the owner component know the HTTP operation
// completed, whether the response code was
// successful or not. Return the response code in the long
// parameter.
PostMessageWithUserDataIntf(
FOwner.winHandleStable,
WM_HTTP_OPERATION_FINISHED,
POSTMESSAGEUSERDATA_LPARAM_IS_INTF,
TRovioCommandIsFinished.Create(
FIdHttpClient.responseCode,
commandName,
strPostData,
FIdHttpClient.ResponseText)
);
finally
FreeAndNil(RBody);
end; // try/finally
except
{
Exceptions that occur during an HTTP operation should not
break the Execute() loop. That would render this thread
inactive. Instead, call the background Exception handler
and only raise an Exception if requested.
}
On E: Exception do
begin
// Default is to raise an Exception. The background
// Exception event handler, if one exists, can
// override this by setting bRaiseException to
// FALSE.
bRaiseException := true;
FOwner.doBgException(E, bRaiseException);
if bRaiseException then
// Ok, raise it as requested.
raise;
end; // On E: Exception do
end; // try/except
end;
// The background thread's Excecute() method and loop (excerpted).
procedure TClientThread_roviosendcmd_.Execute;
var
errMsg: string;
MsgRec : TMsg;
theHttpCliName: string;
intfCommandTodo, intfNewCommandTodo: IRovioSendCommandsTodo_indy;
bSendResultNotification: boolean;
responseBody, S: string;
dwPriority: DWORD;
begin
// Clear the current command todo and the busy flag.
intfCommandTodo := nil;
FOwner.isBusy := false;
intfNewCommandTodo := nil;
// -------- BEGIN: THREAD PRIORITY SETTING ------------
dwPriority := GetThreadPriority(GetCurrentThread);
{$IFDEF THREADDEBUG}
OutputDebugString(PChar(
Format('Current thread priority for the the send-commands background thread: %d', [dwPriority])
));
{$ENDIF}
// On single CPU systems like our Dell laptop, the system appears
// to have trouble executing smooth motion. Guessing that
// the thread keeps getting interrupted. Raising the thread priority
// to time critical to see if that helps.
if not SetThreadPriority(GetCurrentThread, THREAD_PRIORITY_TIME_CRITICAL) then
RaiseLastOSError;
dwPriority := GetThreadPriority(GetCurrentThread);
{$IFDEF THREADDEBUG}
OutputDebugString(PChar(
Format('New thread priority for the the send-commands background thread after SetThreadPriority() call: %d', [dwPriority])
));
{$ENDIF}
// -------- END : THREAD PRIORITY SETTING ------------
// try
// Create the client Indy HTTP component.
theHttpCliName := '(unassigned)';
theHttpCliName := FOwner.Name + '_idhttpcli';
// 1-24-2012: Added empty component name check.
if theHttpCliName = '' then
raise Exception.Create('(TClientThread_roviosendcmd_.Execute) The client HTTP object is nameless.');
FIdHTTPClient := TIdHTTP.Create(nil);
{ If GetMessage retrieves the WM_QUIT, the return value is FALSE and }
{ the message loop is broken. }
while not Application.Terminated do
begin
try
bSendResultNotification := false;
// Reset the variable that detects new commands to do.
intfNewCommandTodo := nil;
{
If we are repeating a command, use PeekMessage so that if
there is nothing in the queue, we do not block and go
on repeating the command. Note, intfCommandTodo
becomes NIL after we execute a single-shot command.
If we are not repeating a command, use GetMessage so
it will block until there is something to do or we
quit.
}
if Assigned(intfCommandTodo) then
begin
// Set the busy flag to let others know we have a command
// to execute (single-shot or looping).
// FOwner.isBusy := true;
{
Note: Might have to start draining the queue to
properly handle WM_QUIT if we have problems with this
code.
}
// See if we have a new command todo.
if Integer(PeekMessage(MsgRec, 0, 0, 0, PM_REMOVE)) > 0 then
begin
// WM_QUIT?
if MsgRec.message = WM_QUIT then
break // We're done.
else
// Recover the command todo if any.
intfNewCommandTodo := getCommandToDo(MsgRec);
end; // if Integer(PeekMessage(MsgRec, FWndProcHandle, 0, 0, PM_REMOVE)) > 0 then
end
else
begin
// Not repeating a command. Block until something new shows
// up or we quit.
if GetMessage(MsgRec, 0, 0, 0) then
// Recover the command todo if any.
intfNewCommandTodo := getCommandToDo(MsgRec)
else
// GetMessage() returned FALSE. We're done.
break;
end; // else - if Assigned(intfCommandTodo) then
// Did we get a new command todo?
if Assigned(intfNewCommandTodo) then
begin
// ----- COMMAND TODO REPLACED!
// Update/Replace the command todo variable. Set the
// busy flag too.
intfCommandTodo := intfNewCommandTodo;
FOwner.isBusy := true;
// Clear the recently received new command todo.
intfNewCommandTodo := nil;
// Need to send a result notification after this command
// executes because it is the first iteration for it.
// (repeating commands only report the first iteration).
bSendResultNotification := true;
end; // if Assigned(intfNewCommandTodo) then
// If we have a command to do, make the request.
if Assigned(intfCommandTodo) then
begin
// Check for the clear command.
if intfCommandTodo.commandName = 'CLEAR' then
begin
// Clear the current command todo and the busy flag.
intfCommandTodo := nil;
FOwner.isBusy := false;
// Return the response as a simple result.
// FOwner.sendSimpleResult(newSimpleResult_basic('CLEAR command was successful'), intfCommandToDo);
end
else
begin
// ------------- SEND THE COMMAND TO ROVIO --------------
// This method makes the actual HTTP request via the TIdHTTP
// Post() method.
responseBody := doPost(
intfCommandTodo.commandName,
intfCommandTodo.cgiScriptName,
intfCommandTodo.userName_auth,
intfCommandTodo.password_auth,
intfCommandTodo.commandString);
// If this is the first or only execution of a command,
// send a result notification back to the owner.
if bSendResultNotification then
begin
// Send back the fully reconstructed response since
// that is what is expected.
S := FIdHTTPClient.Response.ResponseText + CRLF + FIdHTTPClient.Response.RawHeaders.Text + CRLF + responseBody;
// Return the response as a simple result.
FOwner.sendSimpleResult(newSimpleResult_basic(S), intfCommandToDo);
end; // if bSendResultNotification then
// If it is not a repeating command, then clear the
// reference. We don't need it anymore and this lets
// us know we already executed it.
if not intfCommandTodo.isRepeating then
begin
// Clear the current command todo and the busy flag.
intfCommandTodo := nil;
FOwner.isBusy := false;
end; // if not intfCommandTodo.isRepeating then
end; // if intfCommandTodo.commandName = 'CLEAR' then
end
else
// Didn't do anything this iteration. Yield
// control of the thread for a moment.
Sleep(0);
except
// Do not let Exceptions break the loop. That would render the
// component inactive.
On E: Exception do
begin
// Post a message to the component log.
postComponentLogMessage_error('ERROR in client thread for socket(' + theHttpCliName +'). Details: ' + E.Message, Self.ClassName);
// Return the Exception to the current EZTSI if any.
if Assigned(intfCommandTodo) then
begin
if Assigned(intfCommandTodo.intfTinySocket_direct) then
intfCommandTodo.intfTinySocket_direct.sendErrorToRemoteClient(exceptionToErrorObjIntf(E, PERRTYPE_GENERAL_ERROR));
end; // if Assigned(intfCommandTodo) then
// Clear the command todo interfaces to avoid looping an error.
intfNewCommandTodo := nil;
// Clear the current command todo and the busy flag.
intfCommandTodo := nil;
FOwner.isBusy := false;
end; // On E: Exception do
end; // try
end; // while not Application.Terminated do
To make use of HTTP keep-alives correctly, use FIdHTTPClient.Request.Connection := 'keep-alive' instead of FIdHTTPClient.Request.CustomHeaders.Add('Connection: keep-alive'), or set FIdHTTPClient.ProtocolVersion := pv1_1. At least that is how it works in Indy 10. I will double check Indy 9 when I get a chance.
Regardless of which version you use, the robot has to support keep-alives in the first place, or else TIdHTTP has no choice but to make a new socket connection for each request. If the robot sends an HTTP 1.0 response that does not include a Connection: keep-alive header, or an HTTP 1.1 response that includes a Connection: close header, then keep-alives are not supported.
I have an app that makes intensively uses of Wininet functions to get some data from internet. I am getting a very odd handle related error messages sometimes:
Internal error in ConnectToHost when trying to create a session
ERROR_INTERNET_OUT_OF_HANDLES: No more handles could be generated at this time. Wininet error code = 12001;
When this occured i noticed that my application had more than 5000 handles created. I ran a resource profile and I found out that some handles created by wininet were not being freed.
So, I created a small application to reproduce the issue. The code is simple and does nothing but allocate some wininet handles and then free them. That is the code:
procedure request(const AUrl : AnsiString);
var
sMethod : AnsiString;
pSession : HINTERNET;
pConnection : HINTERNET;
pRequest : HINTERNET;
port : Integer;
flags : DWord;
begin
pSession := InternetOpen(nil, INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0);
if Assigned(pSession) then
try
Port := INTERNET_DEFAULT_HTTP_PORT;
pConnection := InternetConnectA(pSession, PAnsiChar(AUrl), port, nil, nil, INTERNET_SERVICE_HTTP, 0, 0);
if Assigned(pConnection) then
try
sMethod := 'GET';
flags := INTERNET_SERVICE_HTTP;
pRequest := HTTPOpenRequestA(pConnection, PAnsiChar(sMethod), PAnsiChar(AUrl), nil, nil, nil, flags, 0);
try
if Assigned(pRequest) then
ShowMessage('ok');
finally
InternetCloseHandle(pRequest);
end;
finally
InternetCloseHandle(pConnection);
end;
finally
InternetCloseHandle(pSession);
end;
end;
Running this sample on my profiler, I get the same handle related issues.
I think that InternetCloseHandle is not freeing the handle as it should be because my resource profile tells me that I have 3 live handles when I close the application. Those are the handles that are not being freed:
pRequest
pConnection
pSession
Does anyone know how to get rid of this?
EDIT
The function InternetCloseHandle is working fine, the return value is true.
EDIT
I have searched a lot on the internet, but i was not able to find anybody complaining about that. But it is happening. I would like to know if anybody reproduced the issue or if it is just me.
It turned out to be a AQtime problem. I downloaded another profiler and I also took a look at Task Manager and it seems that the handles are being released. But I still get the no more handles error sometimes and I have no idea why. But I will open another question, since this one was just to see why those handles were not being released.
Thanks for all help I got.
The Http protocol has some limits and the Wininet is using them.
Check at WinInet limits connections per server:
WinInet limits connections to a single HTTP 1.0 server to four simultaneous connections. Connections to a single HTTP 1.1 server are limited to two simultaneous connections. The HTTP 1.1 specification (RFC2616) mandates the two-connection limit. The four-connection limit for HTTP 1.0 is a self-imposed restriction that coincides with the standard that is used by a number of popular Web browsers.
Maybe you should wait until connections are closed before attemping a new connection
I'm trying to use the TUdpSocket in Delphi. What I want to do is connect to a UDP server, send some data and wait for answer. Data is sent correctly, but the control does not receive anything. I don't know why. I've been struggling with this problem for many hours now and I'm going to give up :-(.
I tried to use TIdUDPClient, but the situation is the same. Data is sent properly, but none is received.
Only does TIdUDPServer work more or less properly for it both sends and receives data. Unfortunately data reception is handled by a separate thread (main or other, depending on the ThreadedEvent property) which forces me to use synchronization and complicate the whole code. I would like to handle UDP connection in my own thread. Just send some data and call WaitForData() to wait for an answer and then handle it in the same thread.
And if opossible, I don't want to use any third party controls, but if it is the only solution, I accept it.
Thank you very, very much for your help in advance.
---- Examples ---
i) TUDPSocket:
var
lR, lW, lE: boolean;
begin
UdpSocket1.LocalPort := '1600';
UdpSocket1.RemotePort := '1600';
UdpSocket1.RemoteHost := '127.0.0.1';
UdpSocket1.Connect;
UdpSocket1.Sendln('test');
UdpSocket1.Select(#lR, #lW, #lE, 2000);
if lR then
ShowMessage(UdpSocket1.Receiveln());
end;
As you can see, the control should receive the data it transmits. And apparently it does, for the lR evaluates to true after the Select() method is called. But Receiveln() returns an empty string, as ReceiveBuf() does. When i start a UDP server and send some data to it, it is received properly, so I'm certain that the data is really sent.
You should need nothing more than this:
function SayHi(Host: String; Port: Integer): String;
var
Client: TIdUDPClient;
begin
Client := TIdUDPClient.Create(nil);
try
Client.Host := Host;
Client.Port := Port;
Client.Send('Hello');
Result := Client.ReceiveString(1000);
finally
Client.Free;
end;
end;
which will, in the same thread, send a UDP packet and either receive a UDP packet or raise an exception after a timeout.
If the above doesn't work, check (using something like Wireshark that the client's actually sending the data. Then check on the server that it's actually receiving the packet.
Send() ultimately (on Windows) calls WinSock's sendto(), and ReceiveString() uses select() and recvfrom(). You could sort've simulate a TIdUDPServer by
while not Terminated do begin
S := Client.ReceiveString(1000);
DoSomething(S);
end;
Another possibility would be to use Synapse for your communication. It is a simple communications library which performs blocking calls, so works well in a single worker thread where you want to stop and wait for data rather than rely on an event to fire. The latest versions in SVN have support for the latest versions of Delphi.
I haven't programmed in Delphi for a while and frankly didn't think I'll ever have to but...
Here I am, desperately trying to find some information on the matter and it's so scarce nowadays, I can't find anything. So maybe you guys could help me out.
Currently my application uses Synapse library to make HTTP calls, but it doesn't allow for setting a timeout. Usually, that's not a big problem, but now I absolutely must to have a timeout to handle any connectivity issues nicely.
What I'm looking for, is a library (synchronous or not) that will allow making HTTP requests absolutely transparent for the user with no visible or hidden delays. I can't immediately kill a thread right now, and with possibility of many frequent requests to the server that is not responding, it's no good.
EDIT: Thanks everybody for your answers!
You will always have to take delays and timeouts into account when doing network communication. The closest you can get IMHO is to put network communication in a thread. Then you can check if the thread finishes in the desired time and if not just let it finish, but ignore the result (there's no safe way to abort a thread). This has an additional advantage: you can now just use synchronous network calls which are a lot easier to read.
In synapse, the timeout is available from the TSynaClient object, which THttpSend decends from. So all you have to do to adjust for timeout (assuming your using the standard functions) is to copy the function your using, add a new parameter and set the Timeout to what you need. For example:
function HttpGetTextTimeout(const URL: string;
const Response: TStrings;
const Timeout:integer): Boolean;
var
HTTP: THTTPSend;
begin
HTTP := THTTPSend.Create;
try
HTTP.Timeout := Timeout;
Result := HTTP.HTTPMethod('GET', URL);
if Result then
Response.LoadFromStream(HTTP.Document);
finally
HTTP.Free;
end;
end;
Synapse defaults to a timeout of 5000 and does timeout if you wait long enough. Since its tightly contained, synapse runs perfectly fine in threads.
[Known to work on D2010 only]
You can use MSXML to send client requests (add msxml and ole2 to your uses clause). The trick is to use IServerXMLHTTPRequest rather than IXMLHTTPRequest, as the former allows timeouts to be specified. The code below shows the Execute() method of a thread:
procedure TClientSendThread.Execute;
const
LResolveTimeoutMilliseconds = 2000;
LConnectTimeoutMilliseconds = 5000;
LSendTimeoutMilliseconds = 5000;
LReceiveTimeoutMilliseconds = 10000;
var
LHTTPServer: IServerXMLHTTPRequest;
LDataStream: TMemoryStream;
LData: OleVariant;
begin
{Needed because this is inside a thread.}
CoInitialize(nil);
LDataStream := TMemoryStream.Create;
try
{Populate ....LDataStream...}
LData := MemoryStreamToOleVariant(LDataStream);
LHTTPServer := CreateOleObject('MSXML2.ServerXMLHTTP.3.0') as IServerXMLHTTPRequest;
LHTTPServer.setTimeouts(
LResolveTimeoutMilliseconds,
LConnectTimeoutMilliseconds,
LSendTimeoutMilliseconds,
LReceiveTimeoutMilliseconds
);
LHTTPServer.open('POST', URL, False, 0, 0);
LHTTPServer.send(LData);
FAnswer := LHTTPServer.responseText;
finally
FreeAndNil(LDataStream);
CoUninitialize;
end;
end;
I recently discovered an extremely annoying behavior of this MSXML technique in which GET requests will not be re-sent if the URL remains unchanged for subsequent sendings; in other words, the client is caching GET requests. This does not happen with POST.
Obviously, once the timeouts occur, the Execute method completes and the thread is cleaned up.
Synapse can be configured to raise an Exception when network errors occur.
RaiseExcept
Check http://synapse.ararat.cz/doc/help/blcksock.TBlockSocket.html#RaiseExcept:
If True, winsock errors raises
exception. Otherwise is setted
LastError value only and you must
check it from your program! Default
value is False.