How to send a DELETE request with Indy? - delphi

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;

Related

Indy10 Google Securetoken always 403

I am struggling to get a response from Google Securetoken using Delphi 10.2.2 and Indy 10.
I've gotten my RefreshToken before from https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key=XXX. This worked fine.
Now I try to refresh the token with https://securetoken.googleapis.com/v1/token, which doesn't work. I always get a 403 Forbidden error.
procedure TForm1.Button2Click(Sender: TObject);
var
l_Response: string;
l_PostData: TIdMultiPartFormDataStream;
l_IRESAccessToken: TIRESAccessToken;
begin
IdHTTP2.Request.ContentType := 'application/json';
IdHTTP2.Request.CharSet := 'utf-8';
l_PostData := TIdMultiPartFormDataStream.Create;
try
l_PostData.AddFormField('grant_type', 'refresh_token');
l_PostData.AddFormField('refresh_token', m_IRESAuth.RefreshToken);
l_PostData.AddFormField('key', 'XXX');
try
l_Response := IdHTTP2.Post('https://securetoken.googleapis.com/v1/token', l_PostData);
l_IRESAccessToken := TJson.JsonToObject<TIRESAccessToken>(l_Response);
except
on E: Exception do
ShowMessage('Error on request: '#13#10 + e.Message);
end;
finally
l_PostData.Free;
end;
end;
I tried an IOHandler that specifies TLS 1.2, and tried to send the post with a JSON object. Also, I set the hoKeepOrigProtocol flag in the TIdHTTP.HTTPOptions. Nothing worked so far, I always get 403 Forbidden.
I tried it with another program, no problems there.
Am I missing something?
Per Google's documentation:
Token Service REST API Reference:
HTTP request
POST https://securetoken.googleapis.com/v1/token
Request body
The request body contains data with the following structure:
URL-encoded representation
grant_type=string&code=string&refresh_token=string
The TStrings overload of TIdHTTP.Post() sends data in that format. That is the overload you need to use, not the TIdMultipartFormDataStream overload.
Also, as you can see above, this URL does not accept your key as input. It only accepts grant_type, code (which is ignored when grant_type is not 'authorization_code') and refresh_token.
Try this instead:
procedure TForm1.Button2Click(Sender: TObject);
var
l_Response: string;
l_PostData: TStringList;
l_IRESAccessToken: TIRESAccessToken;
begin
IdHTTP2.Request.ContentType := 'application/x-www-webform-urlencoded';
l_PostData := TStringList.Create;
try
l_PostData.Add('grant_type=refresh_token');
l_PostData.Add('refresh_token=' + m_IRESAuth.RefreshToken);
try
l_Response := IdHTTP2.Post('https://securetoken.googleapis.com/v1/token', l_PostData);
l_IRESAccessToken := TJson.JsonToObject<TIRESAccessToken>(l_Response);
except
on E: Exception do
ShowMessage('Error on request: '#13#10 + e.Message);
end;
finally
l_PostData.Free;
end;
end;
That being said, after reading the above documentation, I don't believe you can send an idToken from https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword to https://securetoken.googleapis.com/v1/token as a refresh_token. I think you need to send it as an authorization_code instead, which then gives you a refresh_token. So try adding that extra step, if you have not already done so.
Sorry for the late response and thank you for the answer.
After consultation with the Main-API-Developer, he told me that they are using Firebase in the background. So the API request für Google is different.
https://cloud.google.com/identity-platform/docs/use-rest-api#section-sign-in-email-password
https://cloud.google.com/identity-platform/docs/use-rest-api#section-refresh-token
With this missing information the request works fine
procedure TForm1.Button2Click(Sender: TObject);
var
l_Response: string;
l_IRESAccessToken: TIRESAccessToken;
l_Json: string;
l_JsonToSend: TStringStream;
begin
l_Json := '{"grant_type": "refresh_token","refresh_token": "'+ m_IRESAuth.RefreshToken+ '"}';
l_JsonToSend := TStringStream.Create(l_Json, TEncoding.UTF8);
try
try
l_Response := IdHTTP2.Post('https://securetoken.googleapis.com/v1/token?key=XXX, l_JsonToSend);
l_IRESAccessToken := TJson.JsonToObject<TIRESAccessToken>(l_Response);
except
on E: Exception do
ShowMessage('Error on request: '#13#10 + e.Message);
end;
finally
l_JsonToSend.Free;
end;
end;

Delphi - TIdHTTP freezes when POSTing data

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;

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!

Using any exceptions to trigger code in Delphi

Hi I am new to using Delphi and am trying to write an application that will check to see if a website is up or if there is any thing wrong with it. I am using Indy's IdHTT. The problem is that it will catch any protocol errors but not things like socket errors.
procedure TWebSiteStatus.Button1Click(Sender: TObject);
var
http : TIdHTTP;
url : string;
code : integer;
begin
url := 'http://www.'+Edit1.Text;
http := TIdHTTP.Create(nil);
try
try
http.Head(url);
code := http.ResponseCode;
except
on E: EIdHTTPProtocolException do
code := http.ResponseCode;
end;
ShowMessage(IntToStr(code));
if code <> 200 then
begin
Edit2.Text:='Something is wrong with the website';
down;
end;
finally
http.Free();
end;
end;
I am basically trying to catch any thing that is not that the website is ok so I can call another form that will setup an email to tell me that the site is down.
update: First you are right I did miss that 'then' sorry about that was removing other code and it got deleted by mistake. I did not know the specific to general when dealing with exceptions thank you. Finally I did find what i was looking for was this code here
on E: EIdSocketError do
using the uses IdStack
Change your code to either catch all exceptions, or add more specific ones as well:
url := 'http://www.'+Edit1.Text;
http := TIdHTTP.Create(nil);
try
try
http.Head(url);
code := http.ResponseCode;
except
on E: EIdHTTPProtocolException do
begin
code := http.ResponseCode;
ShowMessage(IntToStr(code));
if code <> 200
begin
Edit2.Text:='Something is wrong with the website';
down;
end;
end;
// Other specific Indy (EId*) exceptions if wanted
on E: Exception do
begin
ShowMessage(E.Message);
end;
end; // Added missing end here.
finally
http.Free();
end;
Note that if you're going to handle multiple exception types, it's important to go from most specific to least specific. In other words, if you put less specific (more general types) exceptions first, this is what happens:
try
DoSomethingThatCanRaiseAnException();
except
on E: Exception do
ShowMessage('This one fires always (covers all exceptions)');
on E: EConvertError do
ShowMessage('This one will never happen - never gets this far');
end;
This one will work properly, because it's more specific to less specific. Properly, it would be reversed:
try
DoSomethingThatCanRaiseAnException();
except
on E: EConvertError do
ShowMessage('This one gets all EConvertError exceptions');
on E: Exception do
ShowMessage('This one catches all types except EConvertError');
end;

HTTP.post versus downloadURL using Delphi XE2

I am trying to use the same procedure for two types of downloads. both are working.
I would like to use TDownloadURL or HTTP.Post on both, but cannot determine how. Which method and how to do this? Thank you.
First operation --
procedure TfrmMain.get1Click(Sender: TObject);
var
json: string;
lHTTP: TIdHTTP;
lParamList: TStringList;
result:string;
begin
json := CRLF +
'{' + CRLF +
' "resource_id": "391792b5-9c0a-48a1-918f-2ee63caa1c54",' + CRLF +
' "filters": {' + CRLF +
' "provider_id": 393303' + CRLF +
' }' + CRLF +
'}';
lParamList := TStringList.Create;
try
lParamList.Add('somename='+json);
lHTTP := TIdHTTP.Create(nil);
try
Result := lHTTP.Post('http://hub.Healthdata.gov/api/action/datastore_search', lParamList);
finally
lHTTP.Free;
end;
finally
lParamList.Free;
end;
end;
Second operation --
procedure TfrmMain.get2Click(Sender: TObject);
var
dl: TDownloadURL;
url:string;
begin
url:='http://api.census.gov/data/2010/sf1?key=KEY&get=P0010001,NAME&for=state:*';
dl := TDownloadURL.Create(self);
try
dl.URL := url;
dl.FileName := execpath+'api1.txt'; dl.ExecuteTarget(nil); dl.Free;
except
dl.Free;
end;
end;
TDownloadURL uses the GET HTTP method. TIdHTTP.Post obviously uses the POST method. In general, neither is appropriate for use in place of the other. That's why both methods exist in the first place.
A POST request can include all the information that a GET request does, plus more, which makes it seem like it should be able to do everything GET can do, plus more. However, servers are not required to (and should not be expected to) handle POST requests the same way they do GET.
As the one writing the HTTP client, you're not really in control of the situation. The server dictates which methods it will honor. Clients need to either do what's expected of them or be denied access.
The Indy components support both methods, so if you just want to make your POST code and your GET code look similar, then you can replace TDownloadURL with TIdHTTP.Get.

Resources