I'm having trouble with a POST request to an API which I am not the owner of.
The request must simply post JSON data. Please have a look:
var
RESTRequest : TRESTRequest;
RESTClient : TRESTClient;
Response : TRESTResponse;
contract : TJSONObject;
begin
RESTClient := TRESTClient.Create('URL');
try
RESTRequest := TRESTRequest.Create(nil);
try
contract := TJSONObject.Create;
contract.AddPair(TJSONPair.Create('name','my_first_contract.pdf'));
RESTRequest.Client := RESTClient;
RESTRequest.Method := rmPOST;
RESTRequest.Accept := 'application/json';
RESTRequest.AddParameter('j_token','mytoken',pkHTTPHEADER,poDoNotEncode);
RESTRequest.AddBody(contract);
RESTRequest.Execute;
Response := RESTRequest.Response;
ShowMessage(Response.StatusText + ' : ' + Response.Content);
finally
RESTRequest.Free;
end;
finally
RESTClient.Free;
end;
end;
I obtained this error :
Not Found : {"errors":"Fatal error in JsonConvert. Passed parameter json object in JsonConvert.deserializeObject() is not of type object.\n"}
I've read online that the AddBody() method first serializes its content if it's an object. In this case, the content of the body is my TJSONObject, but when I try to replace that with a String, like this:
var
contract : String;
...
begin
contract := '{"name":"my_first_contract.pdf"}';
...
RESTRequest.AddBody(contract, ctAPPLICATION_JSON);
...
end;
I'm getting the exact same error.
So, does that mean that a TJSONObject is not viewed as an Object for the JsonConvert.deserializeObject() method ? Or, is the serialization of the AddBody() messed up?
The problem was on the 'j_token' header : as I was trying to solve it, some friends wanted to help me but I didn't want to give them the access token because it's exclusive to my company. They still tried to access the api with a false token wich resulted with the same error as I was getting :
Not Found : {"errors":"Fatal error in JsonConvert. Passed parameter json object in JsonConvert.deserializeObject() is not of type object.\n"}
Thanks to that I could deduce that the issue was on the j_token. After setting up my own api I could watch what was I posting and then I saw that my 'j_token' header was still getting encoded even though I added the poDoNotEncode options to my AddParameter method.
I created a new post on this forum to look for that poDoNotEncode error if you ever stumble upon this same problem : Trouble with poDoNotEncode option in TRESTRequest.AddParameter() method
Related
I'm trying to upload a Document to an api.
The Api owner sent me his C# Code to upload a Document :
var client = new
RestClient("hisurl");
client.Timeout = -1;
var request = new RestRequest(Method.POST);
request.AddHeader("Content-Type", "multipart/form-data");
request.AddHeader("v", "4.2");
request.AddHeader("j_token", "histoken");
request.AddFile("file", "/C:/Users/olivm/Documents/QA API Swagger/smartpacte2.pdf","application/.pdf");
IRestResponse response = client.Execute(request);
Console.WriteLine(response.Content);
I decided to use the TCustomRESTRequest.AddFile() method as well so here is my code :
procedure AttachDocument(contract_id : Integer; pathFile : String);
var
RESTClient : TRESTClient;
RESTRequest : TCustomRESTRequest;
Response : TCustomRESTResponse;
begin
RESTClient := TRESTClient.Create('');
RESTRequest := TCustomRESTRequest.Create(nil);
try
RESTClient.BaseURL := 'myurl';
RESTRequest.Client := RESTClient;
RESTRequest.Accept := 'application/json';
RESTRequest.Params.AddHeader('j_token','mytoken',[poDoNotEncode]);
RESTRequest.Method := rmPOST;
RESTRequest.AddFile('file',pathFile);
RESTRequest.Execute;
Response := RESTRequest.Response;
ShowMessage(Response.Content);
finally
RESTRequest.Free;
RESTClient.Free;
end;
end;
But this result in a error :
RUNTIME ERROR : Impossible converting variant of type (UnicodeString) to type (Int64)
In fact, it seems like the TCustomRESTRequest.AddFile()method wants my pathFile as the first argument and a Int64 as the second one like this :
RESTRequest.AddFile(pathFile,0);
I also tried this :
RESTRequest.AddFile(pathFile,ctAPPLICATION_PDF);
But each and every time i get this error from the api :
{"errors":"no files found"}
Do you have any idea on how to make this work ?Also, have you ever seen this sort of TCustomRESTRequest.AddFile()method ?
Drop a TRESTClient, TRESTRequest and TRESTRepsonse onto a form.
Set the RESTClient.BaseURL, RESTRequest.Method and Resource, also add a "Content-Type" header parameter with a value of "application/json".
Add a JSON string using RESTRequest.AddBody, then view the RESTRequest.ContentType.
It shows ctAPPLICATION_X_WWW_FORM_URLENCODED instead of ctAPPLICATION_JSON. This causes the server to return an error when RESTRequest.Execute() is run.
How do I force the request to use the correct content type when the property cannot be assigned to?
After looking at the REST.Client source code, if you specify the content type using this:
AParameter := RESTRequest.Params.AddItem;
AParameter.ContentType := ctAPPLICATION_JSON;
AParameter.name := 'Content-Type';
AParameter.Value := 'application/json';
Instead of this:
RESTRequest.Params.AddHeader('Content-Type', 'application/json');
Then the TRESTRequest.ContentType property returns with the correct value, and this is the value used during TRESTRequest.Execute.
Another way to force the TRESTRequest.ContentType to be correct when using a body is to add the body text this way:
RESTRequest.Body.Add(AJSONString, ctAPPLICATION_JSON);
This works for me:
client.AddParameter('Content-Type', 'multipart/form-data', pkHTTPHEADER, [poDoNotEncode]);
I am trying to insert an event into my Google Calendar via the Delphi REST controls, but I am uncertain where to add the access token to my request. My Code looks like this:
var
evt : String;
begin
ResetRESTComponentsToDefaults;
RESTClient.BaseURL := 'https://www.googleapis.com/calendar/v3';
RESTClient.Authenticator := OAuth2_GoogleCalendar;
RESTRequest.Resource := 'calendars/primary/events';
evt:='{"summary":"test","description":"test","id":"06824945162f4204bfdc041ae1bbae85","start":{"date":"2018-04-10"},"end":{"date":"2018-04-10"},"guestsCanInviteOthers":false,"visibility":"private"}'
RESTRequest.AddParameter('access_token',OAuth2_GoogleTasks.AccessToken,pkHTTPHEADER);
RESTRequest.Method := TRESTRequestMethod.rmPOST;
RESTRequest.Body.Add(evt,ctAPPLICATION_JSON);
RESTRequest.Execute;
end;
The scope is https://www.googleapis.com/auth/calendar
If I send it in like this I this error:
{
"error":
{
"errors":
[
{
"domain":"global",
"reason":"required",
"message":"Login Required",
"locationType":"header",
"location":"Authorization"
}
]
,
"code":401,
"message":"Login Required"
}
}
Appending ?access_token={accessToken} to the end of the url i get
error 400, parseError.
Where should I add the access token to the request?
I cant help much with Delphi its been years since i have used it, but you have a two options with regard to adding your access token.
First is to just add it as a parameter on the base url
?access_token=TokenHere
The second option is to send it as an authorization header on your request its a bearer token.
Authorization : Bearer cn389ncoiwuencr
After a bit of googling i found this
FIdHTTP.Request.CustomHeaders.FoldLines := False;
FIdHTTP.Request.CustomHeaders.Add('Authorization:Bearer ' + txtToken.Text);
I am trying to insert an event into my google calendar using the delphi REST controls.
This is the code so far:
procedure TForm1.TestGoogleRestParams;
var
i: Integer;
jsonObjEventResource,jsonObjStart,jsonObjEnd: TJSONObject;
begin
try
jsonObjEventResource:=TJSONObject.Create();
jsonObjStart:=TJSONObject.Create();
jsonObjEnd:=TJSONObject.Create();
jsonObjEventResource.AddPair(TJSONPair.Create('summary','test'));
jsonObjEventResource.AddPair(TJSONPair.Create('description','Testing'));
jsonObjEventResource.AddPair(TJSONPair.Create('id',LowerCase('06824945162F4204BFDC041AE1BBAE85')));
jsonObjStart.AddPair(TJSONPair.Create('date',FormatDateTime('yyyy-mm-dd',Now)));
jsonObjEventResource.AddPair(TJSONPair.Create('start',jsonObjStart));
jsonObjEnd.AddPair(TJSONPair.Create('date',FormatDateTime('yyyy-mm-dd',Now)));
jsonObjEventResource.AddPair(TJSONPair.Create('end',jsonObjEnd));
jsonObjEventResource.AddPair(TJSONPair.Create('guestsCanInviteOthers',TJSONBool. Create(false)));
jsonObjEventResource.AddPair(TJSONPair.Create('visibility','private'));
mem_Test.Lines.Add(TJson.Format(jsonObjEventResource));
//mem_Test.Lines.Add(jsonObjEventResource.ToJSON);
RESTRequest.Method := TRESTRequestMethod.rmPOST;
RESTRequest.Body.ClearBody;
RESTRequest.AddBody(jsonObjEventResource);
RESTRequest.Execute;
finally
//jsonObjEventResource.Free;
//jsonObjStart.Free;
//jsonObjEnd.Free;
end;
end;
The Scope I am using is: https://www.googleapis.com/auth/calendar.
BaseURL : https://www.googleapis.com/calendar/v3
ResourceURI : calendars/primary/events
I do get an access token and a refresh token but I cannot Post the Request. This is the error i recieve:
{
"error":
{
"errors":
[
{
"domain":"global",
"reason":"required",
"message":"Login Required",
"locationType":"header",
"location":"Authorization"
}
]
,
"code":401,
"message":"Login Required"
}
}
With the following uri: https://www.googleapis.com/calendar/v3/calendars/primary/events
How can i fix this?
If I don't call this method and just call RESTRequest.Execute; I get a list of all my existing events.
":"Invalid Credentials"
Means that the client id and client secret that you are using are incorrect. You should download a new file from Google Developer console and try again.
Option 2:
Try authenticating the user it could be that the access token has expired and needs to be refreshed
I'm implementing a REST client application which communicates with the Coinbase GDAX Trading API in the JSON format (www.coinbase.com and https://docs.gdax.com/#introduction). I'm facing a problem in signing REST request messages with POST. Signing REST request messages with GET is working fine. Mainly because GET messages don't have a body parameter, which would be part of the signature when available. And this body parameter drives me crazy :-) I'm struggling to get the JSON string stored in the body parameter of a REST request (TCustomRESTRequest.Body). I need this JSON string to sign the REST request message properly. If I pass the body JSON string outside of TCustomRESTRequest.Body (ugly workaround), I get HTTP error 400 (bad request) with additional information "invalid signature". I assume the JSON string in TCustomRESTRequest.Body is somehow altered from the original JSON string. What I would like to achieve is to read out the JSON string directly from TCustomRESTRequest.Body, and then do the signing.
All the authentication and signing I'm doing in my class TCoinbaseAuthenticator (which is inherited from TCustomAuthenticator), or more specific in the DoAuthenticate method:
procedure TCoinbaseAuthenticator.DoAuthenticate(ARequest: TCustomRESTRequest);
var
DateTimeUnix: Int64;
DateTimeUnixStr: string;
Sign: string;
HttpMethod: string;
BodyStr: string;
begin
inherited;
ARequest.Params.BeginUpdate;
try
DateTimeUnix := DateTimeToUnix(TTimeZone.Local.ToUniversalTime(Now));
DateTimeUnixStr := IntToStr(DateTimeUnix);
HttpMethod := HttpMethodToString(ARequest.Method);
// BodyStr := ARequest.Body ... << here I'm strugging to get the JSON string
Sign := GenerateSignature(DateTimeUnixStr, HttpMethod, '/' + ARequest.Resource, BodyStr);
ARequest.AddAuthParameter('CB-ACCESS-KEY', FAPIKey, TRESTRequestParameterKind.pkHTTPHEADER, [TRESTRequestParameterOption.poDoNotEncode]);
ARequest.AddAuthParameter('CB-ACCESS-SIGN', Sign, TRESTRequestParameterKind.pkHTTPHEADER, [TRESTRequestParameterOption.poDoNotEncode]);
ARequest.AddAuthParameter('CB-ACCESS-TIMESTAMP', DateTimeUnixStr, TRESTRequestParameterKind.pkHTTPHEADER, [TRESTRequestParameterOption.poDoNotEncode]);
ARequest.AddAuthParameter('CB-ACCESS-PASSPHRASE', FAPIPassphrase, TRESTRequestParameterKind.pkHTTPHEADER, [TRESTRequestParameterOption.poDoNotEncode]);
ARequest.AddAuthParameter('CB-VERSION', '2015-07-22', TRESTRequestParameterKind.pkHTTPHEADER, [TRESTRequestParameterOption.poDoNotEncode]);
ARequest.AddAuthParameter('Content-Type', 'application/json', TRESTRequestParameterKind.pkHTTPHEADER, [TRESTRequestParameterOption.poDoNotEncode]);
finally
ARequest.Params.EndUpdate;
end;
end;
Here is my signature generating function (I think the code is ok, because it works fine if I don't have to consider the body parameter, e.g. for GET requests):
function TCoinbaseAuthenticator.GenerateSignature(ATimeStamp: string; AMethod: string; AURL: string; ABody: string): string;
var
s: string;
SignStr: string;
BitDigest: T256BitDigest;
key: string;
begin
s := ATimeStamp+AMethod+AURL+ABody;
key := MimeDecodeString(FAPISecret);
BitDigest := CalcHMAC_SHA256(key, s);
SignStr := SHA256DigestAsString(BitDigest);
SignStr := MimeEncodeStringNoCRLF(SignStr);
Result := SignStr;
end;
Some more insights of the Coinbase (GDAX) message signing process:
All REST requests must contain the following headers:
CB-ACCESS-KEY: The api key as a string (created by Coinbase)
CB-ACCESS-SIGN: The base64-encoded signature
CB-ACCESS-TIMESTAMP: A timestamp for your request
CB-ACCESS-PASSPHRASE: The passphrase you specified when creating the API key
The CB-ACCESS-SIGN header is generated by creating a sha256 HMAC using the base64-decoded secret key on the prehash string timestamp + method (GET,POST, etc.) + requestPath + body (where + represents string concatenation) and base64-encode the output. The timestamp value is the same as the CB-ACCESS-TIMESTAMP header.
The body is the request body string or omitted if there is no request body (typically for GET requests).
Thanks a lot for any help!
Thanks a lot for your comment, Remy! I did indeed use the wrong charset for the signature calculation. So at least my super ugly workaround (passing the body JSON string outside of TCustomRESTRequest.Body into the authenticator class) is working.
For the first part of the problem I don't think there is an easy solution. I got some support from Embarcadero which I would like to share with others who have a similar problem.
Looking at the TBody class, which is used for the Body property, this looks like it only has properties to write to - there isn't anything that will help in reading it back. I found that if I added a body to a TRequest, this created a TRestRequestParameter with a name of body. I would therefore suggest that you can get to it via RestRequest1.Params.ParameterByName('body');
Unfortunately this proposal is not working. Reason:
The parameter "body" is stored in the body request parameter list (TCustomRESTRequest.FBody.FParams), and not in RESTRequest parameter list (TCustomRESTRequest.Params). Since TCustomRESTRequest.FBody.FParams doesn't have a public property, I cannot access the field from outside. Later the parameters are copied from TCustomRESTRequest.FBody.FParams to TCustomRESTRequest.Params, but this is too late in my case, because the authenticator (where I calculate the signature) is called earlier. Meaning in my TCoinbaseAuthenticator.DoAuthenticate(ARequest: TCustomRESTRequest) method the parameter list in ARequest is still empty.