Delphi - TIdHTTP freezes when POSTing data - delphi

I have an issue developing an HTTP client using Indy's TIdHTTP component. I'm using Indy 10.5.9.0.
When I call the TIdHTTP.DoRequest() method, the component freezes, and raises an exception:
Connection closed gracefully
I already tried using the TIdHTTP.Post() method, but the same problem happens.
The problem doesn't happen using the TIdHTTP.Get() method.
Here is my code:
try
jsonLatestFirmware := TJSONObject.ParseJSONValue(httpClient.Get(strGetLatest)) as TJSONObject;
try
srv := 'http://' + currentAlive.IP + ':9335/upgrade?command=start';
sList := TStringList.Create;
sList.Text := jsonLatestFirmware.get('data').JsonValue.ToString;
fileName := ExtractFilePath(Application.ExeName) + 'finals\' + currentMACQR + '-' + currentAlive.IP + '.json';
sList.SaveToFile(fileName);
JsonRetorno := TStringStream.Create('');
try
JsonToSend := TStringStream.Create;
WriteStringToStream(JsonToSend,TIdHTTPAccess(httpClient).SetRequestParams(sList, TEncoding.UTF8));
JsonToSend.Position := 0;
TIdHTTPAccess(httpClient).DoRequest('POST',srv,JsonToSend,jsonRetorno,[]); //component freezes here...
strRetorno := TStringList.Create();
strRetorno.LoadFromStream(JsonRetorno);
lblInformacao.Visible := True;
ShowMessage(strRetorno.Text);
except
on E: Exception do
ShowMessage('Error on request: '#13#10 + E.Message);
end;
finally
sList.Free;
JsonRetorno.Free;
JsonToSend.Free;
jsonResponse.Free;
end;
except
on E: Exception do
ShowMessage('There are no firmwares available for this product.');
end;
Anyone can help me to solve this problem?

I'm using Indy 10.5.9.0.
That is a very old and outdated version. At the time of this writing, the latest version is 10.6.2.5455. Please upgrade, as there have been a lot of changes and fixes to Indy since 10.5.9.
When I call the TIdHTTP.Post() method, the component freezes, and raises an exception:
First, you are not calling TIdHTTP.Post(), you are calling TIdHTTP.DoRequest(). You should be calling TIdHTTP.Post() instead, which calls TIdHTTP.DoRequest() internally for you:
httpClient.Post(srv, JsonToSend, jsonRetorno);
Second, Post() can't freeze and raise an exception at the same time. Which is actually happening? How large is the JSON you are posting? Does the server send any kind of response before the exception is raised?
Third, you absolutely should NOT be calling TIdHTTP.SetRequestParams() at all. That method is meant for internal use by the TStrings overloaded version of TIdHTTP.Post() when it submits an HTML webform in application/x-www-webform-urlencoded format. That is NOT what you need in this situation. You need to post your JSON as-is, not webform-encode it.
Indy uses blocking sockets. Most operations in Indy are synchronous. TIdHTTP.Post() is no different. It blocks the calling thread until the HTTP post is finished. If it freezes, that means the post is taking a long time to send, or the server is not sending a valid response back. If TIdHTTP.Post() raises a "Connection closed gracefully" exception, that means the server has closed the socket connection on its end while TIdHTTP was still sending/reading data over the socket. That could happen, for instance, if the server doesn't like your POST request, or if it determines the JSON is invalid while it is being received.
Also, your code can be simplified in general. You are misusing TStringList for handling JSON data.
Try something more like this instead:
var
sJSON, sRetorno: string;
jsonLatestFirmware: TJSONValue;
JsonToSend: TStringStream;
...
begin
...
try
sJSON := httpClient.Get(strGetLatest);
except
ShowMessage('There are no firmwares available for this product.');
Exit;
end;
try
jsonLatestFirmware := TJSONObject.ParseJSONValue(sJSON);
try
sJson := (jsonLatestFirmware as TJSONObject).get('data').JsonValue.ToString;
finally
jsonLatestFirmware.Free;
end;
JsonToSend := TStringStream.Create(sJson, TEncoding.UTF8);
try
JsonToSend.SaveToFile(ExtractFilePath(Application.ExeName) + 'finals\' + currentMACQR + '-' + currentAlive.IP + '.json');
httpClient.Request.ContentType := 'application/json';
sRetorno := httpClient.Post('http://' + currentAlive.IP + ':9335/upgrade?command=start', JsonToSend);
finally
JsonToSend.Free;
end;
except
on E: Exception do
ShowMessage('Error: '#13#10 + E.Message);
Exit;
end;
lblInformacao.Visible := True;
ShowMessage(sRetorno);
...
end;

Related

Error on Indy HTTP post using TStringList as sending parameter

I have created a little program which access a webservice of mail service. The same code works on Delphi 7 with indy 9, but doesn't works with Delphi Seattle with Indy 10.
My Stringlist is built that way:
ParametrosConsulta.Values['nCdEmpresa'] := Edt_Cod.Text;
ParametrosConsulta.Values['&sDsSenha'] := Edt_Sen.Text;
...
My post have a sending parameter a stringlist, which has a text like that:
nCdEmpresa= &sDsSenha= &nCdServico=41106&sCepOrigem=88905355&sCepDestino=88906768&nVlPeso=20.0&nCdFormato=1&nVlComprimento=20.0&nVlAltura=20.0&nVlLargura=20.0&nVlDiametro=6.0&sCdMaoPropria=N&nVlValorDeclarado=0&sCdAvisoRecebimento=N
then i call idHttp.Post like that, which ParametroConsulta holds the text i've shown before, and Resposta is a TStringStream which holds the response of the request:
IdHttp.Request.Clear;
IdHttp.Request.Host := 'ws.correios.com.br';
IdHttp.Request.ContentType := 'application/x-www-form-urlencoded';
idHTTP.Request.UserAgent := 'Mozilla/3.0 (compatible;Indy Library)';
IdHTTP.Request.Charset := 'utf-8';
IdHTTP.ProtocolVersion := pv1_1;
{...}
try
Application.ProcessMessages;
FrmPrincipal.Refresh;
IdHttp.Post('http://ws.correios.com.br/calculador/CalcPrecoPrazo.asmx/CalcPrecoPrazo', ParametrosConsulta, Resposta);
except
on E:EIdHttpProtocolException do
begin
ShowMessage(E.Message + ' - ' + E.ErrorMessage);
PnlEnviando.Visible := False;
Exit;
end;
end;
But after post, the webservice returns that sDsSenha is missing (this parameter can hold a empty space).
Don't write &:
ParametrosConsulta.Values['sDsSenha'] := Edt_Sen.Text;
Ampersand are adding by Indy automatically. Btw, you may need to use TIdURI.PathEncode(Edt_Sen.Text) for escaping some characters.

How to check if TIdHTTP exception is raised

I'm not sure if this is the best way to check if a TIdHTTP exception is raised, here is what I did:
HTTP := TIDHttp.Create(nil);
try
try
HTTP.Head(URL);
SizeF := HTTP.Response.ContentLength;
code := HTTP.ResponseCode;
except
on E: Exception do
begin
code := HTTP.ResponseCode;
ShowMessage(IntToStr(code));
end;
end;
finally
HTTP.Free;
end;
if code = 200 then
// go download the file using multiple threads.
What I want to achieve is raising an axception is case there is one (I guess I already did) otherwise the program keeps running and download the file.
So is this the correct way to do it? Thanks for your replies.
As a common rule: Only handle exceptions when you can and want to handle them.
Otherwise let them just flow to stop the current execution of your code. Without any interception you will just receive a dialog with the exception message (this is handled by TApplication).
In this case you can change your code to
HTTP := TIDHttp.Create(nil);
try
HTTP.Head(URL);
// if an exception is raised then the rest of the code will not be executed
// yes, the code in finally part will execute
SizeF := HTTP.Response.ContentLength;
code := HTTP.ResponseCode;
finally
HTTP.Free;
end;
// check if all conditions are met
if code <> 200 then
// if not, raise a custom exception as you like
raise Exception.Create( 'Not what I expected here' );
// go download the file using multiple threads.

Delphi and indy TIDHTTP : distinguish between "server not found" and "not found" response error

I am using indy TIDHTTP to code a way to know whether my server on the internet is down or the address of the page on the same server is not available.
I copied the suggestion given in another thread on stackoverflow:
try
IdHTTP1.Get(mypage_address);
except
on E: EIdHTTPProtocolException do begin
if e.errorcode=404 then
showmessage('404 File not found');
// use E.ErrorCode, E.Message, and E.ErrorMessage as needed...
end;
end;
but this way I am only able to detect a server response code and not whether the server did not respond at all. I guess it's trivial but I do not know what is the way to do that?
An EIdHTTPProtocolException exception is raised when TIdHTTP successfully sends a request to the server and it sends an HTTP error reply back to TIdHTTP. If the server cannot be reached at all, a different exception (typically EIdSocketError, EIdConnectException, or EIdConnectTimeout) will be raised instead.
try
IdHTTP1.Head(mypage_address);
except
on E: EIdHTTPProtocolException do begin
ShowMessage(Format('HTTP Error: %d %s', [E.ErrorCode, E.Message]));
end;
on E: EIdConnectTimeout do begin
ShowMessage('Timeout trying to connect');
end;
on E: EIdSocketError do begin
ShowMessage(Format('Socket Error: %d %s', [E.LastError, E.Message]));
end;
on E: Exception do begin
ShowMessage(Format('Error: [%s] %s', [E.ClassName, E.Message]));
end;
end;
I attempted doing the server/site checking scientifically. but eventually simply came down to this:
function TFrameSiteChecker.GetSiteHeader(const AUrl: string): Integer;
begin
try
idhttp1.Head(AUrl);
Result := idhttp1.ResponseCode;
except
on E: exception do
Result := 0;
end;
end;
Logic being, getting the head reduces traffic, log sizes etc.
There is one one right result from the function - the return of status code 200, anything else is a fail.
Also I failed to force windows / the system / indy to not buffer/cache content, so also eventually, just run the checker every 30 minutes on a schedule. Otherwise (unless something else clears the cache) after the first connect it always succeeds, even if you unplug the machine from the network!

How to send a DELETE request with Indy?

I tried several search for examples on the web on how to send a DELETE request with TidHTTP without success...
I'm working with Delphi Indy 10.0.52.
Apparently there is not such a method as Delete in the TIdHTTP object.
Is it possible?
I'm missing something here... Help!
I ended up with something (not working) like this:
procedure TRestObject.doDelete( deleteURL:String );
var
res : TStringStream;
begin
res := TStringStream.Create('');
http := TidHTTP creation and init...
try
http.Request.Method := IdHTTP.hmDelete;
http.Put( deleteURL, res );
except
on E: EIdException do
showmessage('Exception (class '+ E.ClassName +'): ' + E.Message);
on E: EIdHTTPProtocolException do
showmessage('Protocol Exception (HTTP status '+ IntToStr(E.ErrorCode) +'): ' + E.Message);
on E: EIdSocketError do
showmessage('Socket Error ('+ IntToStr(E.LastError) +'): ' + E.Message);
end;
res.Free;
end;
This is a method in an object already handling GET, PUT, POST to a RESTful web service implemented with django-tastypie. I have all permissions and authentications set in the object's init phase.
As its name suggests, TIdHTTP.Put() forces the request method to PUT. So you cannot use it to send other requests.
10.0.52 is a very old version, you really should upgrade to the latest 10.6.0, which has a TIdHTTP.Delete() method:
http.Delete(deleteURL, res);
If that is not an option, then to send a custom request with 10.0.52, you will have to call TIdHTTP.DoRequest() instead. However, DoRequest() is declared as protected so you will have to use an accessor class to call it, eg:
type
TIdHTTPAccess = class(TIdHTTP)
end;
TIdHTTPAccess(http).DoRequest('DELETE', deleteURL, nil, res, []);
You can check this delphi rest client
https://github.com/fabriciocolombo/delphi-rest-client-api
Look in file HttpConnectionIndy.pas how is delete implemented.
procedure TIdHTTP.Delete(AURL: string);
begin
try
DoRequest(Id_HTTPMethodDelete, AURL, Request.Source, nil, []);
except
on E: EIdHTTPProtocolException do
raise EHTTPError.Create(e.Message, e.ErrorMessage, e.ErrorCode);
end;
end;

How do I get a stack trace from a handled/caught exception and dump it to a trace log

We've created a Datasnap service (with Delphi XE), using Bob Swart's white paper as a guide. It works fine, and we've deployed it to our test server.
Now a problem occurs, when we have executed a large number of requests (through JMeter), some sort of memory corruption occurs. Some requests succeed, some fail with an access violation. In the end, it has become so corrupt, that every request to our OWN (not the DSAdmin) methods responds with an access violation.
However, I can't get my hands on a stacktrace to get more info, because the exception is already catched in the processing of the request.
If I test heavily with the VCL version of this application, it remains working correctly.
Has anyone any clue what this could be, or experienced the same problem, or can you help me get the stack trace from a caught exception (in someone else's code, which I can't edit)?
Thanks in advance.
To log both caught and uncaught exceptions using JEDI JCL, you should install the JEDI JCL.
Then try some code like this code taken from jcl\examples\windows\debug\framestrack\FramesTrackDemoMain.pas:
You should compile with full Debug information on in both the Compiler and Linker options in your delphi project options, for this to work.
Note that you don't have to call LogException, it's called automatically one you've added the exception notifier callback (JclAddExceptNotifier). don't forget to also call JclRemoveExceptNotifier, when the form or data module you are adding it from is destroyed, as shown here:
procedure TForm1.LogException(ExceptObj: TObject; ExceptAddr: Pointer; IsOS: Boolean);
var
TmpS: string;
ModInfo: TJclLocationInfo;
I: Integer;
ExceptionHandled: Boolean;
HandlerLocation: Pointer;
ExceptFrame: TJclExceptFrame;
begin
TmpS := 'Exception ' + ExceptObj.ClassName;
if ExceptObj is Exception then
TmpS := TmpS + ': ' + Exception(ExceptObj).Message;
if IsOS then
TmpS := TmpS + ' (OS Exception)';
mmLog.Lines.Add(TmpS);
ModInfo := GetLocationInfo(ExceptAddr);
mmLog.Lines.Add(Format(
' Exception occured at $%p (Module "%s", Procedure "%s", Unit "%s", Line %d)',
[ModInfo.Address,
ModInfo.UnitName,
ModInfo.ProcedureName,
ModInfo.SourceName,
ModInfo.LineNumber]));
if stExceptFrame in JclStackTrackingOptions then
begin
mmLog.Lines.Add(' Except frame-dump:');
I := 0;
ExceptionHandled := False;
while (chkShowAllFrames.Checked or not ExceptionHandled) and
(I < JclLastExceptFrameList.Count) do
begin
ExceptFrame := JclLastExceptFrameList.Items[I];
ExceptionHandled := ExceptFrame.HandlerInfo(ExceptObj, HandlerLocation);
if (ExceptFrame.FrameKind = efkFinally) or
(ExceptFrame.FrameKind = efkUnknown) or
not ExceptionHandled then
HandlerLocation := ExceptFrame.CodeLocation;
ModInfo := GetLocationInfo(HandlerLocation);
TmpS := Format(
' Frame at $%p (type: %s',
[ExceptFrame.ExcFrame,
GetEnumName(TypeInfo(TExceptFrameKind), Ord(ExceptFrame.FrameKind))]);
if ExceptionHandled then
TmpS := TmpS + ', handles exception)'
else
TmpS := TmpS + ')';
mmLog.Lines.Add(TmpS);
if ExceptionHandled then
mmLog.Lines.Add(Format(
' Handler at $%p',
[HandlerLocation]))
else
mmLog.Lines.Add(Format(
' Code at $%p',
[HandlerLocation]));
mmLog.Lines.Add(Format(
' Module "%s", Procedure "%s", Unit "%s", Line %d',
[ModInfo.UnitName,
ModInfo.ProcedureName,
ModInfo.SourceName,
ModInfo.LineNumber]));
Inc(I);
end;
end;
mmLog.Lines.Add('');
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
JclAddExceptNotifier(Form1.LogException);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
JclRemoveExceptNotifier(Form1.LogException);
end;
This is the usual initialization code:
initialization
JclStackTrackingOptions := JclStackTrackingOptions + [stExceptFrame];
JclStartExceptionTracking;
Here's the JCL FramesTrackExample.dproj demo running:
For your purposes, change the code that adds a line to TMemo.Lines, to write to a log file on disk instead. If you already have a logging system, that's great, and if not, then consider Log4D.
Every new web service call is a new thread. Some resources can be allocated by previous thread when the next service call is coming and the new thread tries to access them. Or some resources may be released by one thread when another thread tries to use them. You should use TCriticalSection to make sure that all resources are available only for one thread. Also make sure that the TCriticalSection is a global variable and accessible by all instances.

Resources