I'm a beginner and trying to retrieve a response from a webservice.
The method (ImportSoap) to send a base64code's pdf and i use a code like this:
procedure TForm1.Button2Click(Sender: TObject);
var
wf:ImportSoap;
res:ArrayOfString;
begin
wf:=(httprio1 as ImportSoap);wf.UploadChunk('*user*','*password*',convertStringToTByteDynArray(Codifica64(Edit1.Text)),res[0],error);
memo1.Lines.Text:=risposta;
end;
The error is:
Server was unable to process request - String reference not set to an instance of a String
Server-side code is:
procedure UploadChunk(const userLogin: WideString; const userPassword: WideString; const data: TByteDynArray; out UploadChunkResult: WideString; out errorDescription: WideString); stdcall;
Can anyone help me?
(sorry for my english)
Related
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
I'm currently building a restful API server for my mobile devices to access a mySQL database. The mobile apps need to be able to get data from the server, and also insert and edit data.
I have managed to successfuly do a GET from the server, but I'm stuck at how to get do the post. On the WebModuleUnit I added an action, with the following properties:
MethodType mtGet
Name actImsDocuments
PathInfo /imsDocuments
procedure TWebModule1.WebModule1actImsDocumentsAction(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
var
qryDocuments: TFDQuery;
JsonArray: TJSONArray;
JsonObject: TJSONObject;
begin
// Configure response
Response.ContentType := 'application/json; charset=utf-8';
//Search for my clients table
qryDocuments := TFDQuery.Create(nil);
with qryDocuments do
begin
Connection := FDConnection1;
Active := false;
SQL.Clear;
Open('SELECT * FROM ims_document');
if qryDocuments.RecordCount > 0 then //if there is items
begin
JsonArray := TJSONArray.Create;
try
First;
while not EoF do //Iterate all items in the FDQuery
begin
JsonObject := TJSONObject.Create;
AddFieldsToJSON(JsonObject, qryDocuments);
JsonArray.AddElement(JsonObject);
Next;
end;
finally
Response.Content := JsonArray.ToString;
JsonArray.DisposeOf;
end;
end;
end;
end;
procedure TWebModule1.AddFieldsToJSON(AJsonObject: TJSONObject;
AQuery: TFDQuery);
begin
with AQuery do
begin
AJsonObject.AddPair('number', TJSONNumber.Create(FieldByName('number').AsInteger));
AJsonObject.AddPair('document_title', FieldByName('document_title').AsString);
end;
end;
Now this all above works perfectly, what I have no idea how to do, is how do I for example, edit a record in the table, or insert a new record, how do I get the values from the mobile device. I have no idea how to do a post, or update. Can anyone just give me an example of code please, as I have looked everywhere and just cannot get a clear understanding of this.
If I change the property of the action to mtPost, how would my code differ If I wanted to add a record to the table?
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.