Passing a JSON object to Datasnap Server application - json

I have a datasnap application server and a client witten in Delphi Tokyo 10.2. I need to know whether I do the communication between the two correctly. I will write down the client code here :
Client Code:
procedure TRemoteAcessModule.InitialiseRESTComponents(BaseURL: string);
var
ReqParam : TRESTRequestParameter;
begin
//URL to which client has to connect
RESTClient1.BaseURL := BaseURL;//'http://192.168.110.160:8080/datasnap/rest/TserverMethods1';
RESTClient1.Accept:= 'application/json, text/plain; q=0.9, text/html;q=0.8,';
RESTClient1.AcceptCharset := 'UTF-8, *;q=0.8';
RESTRequest1.Client := RESTClient1;
RESTRequest1.Response := RESTResponse1;
RESTResponse1.RootElement := 'Rows';
RESTResponseDataSetAdapter1.Response := RESTResponse1;
RESTResponseDataSetAdapter1.Dataset := FDMemTable1;
RESTResponseDataSetAdapter1.NestedElements := true;
end;
class function TItemsHelper.InsertItem(item: TItem): boolean;
var
ds : TDataset;
begin
ds := RemoteAcessModule.CallResource2('InsertItem', TJson.ObjectToJsonString(item));
if ds.Fields[0].AsInteger > 0 then
result := true
else
result := false
end;
function TRemoteAcessModule.CallResource2(ResourceName: string): TDataset;
begin
CallResourceNoParams(ResourceName);
result := GetQueryResult;
end;
procedure TRemoteAcessModule.CallResource(ResourceName, Params: string);
begin
RESTResponseDataSetAdapter1.Dataset := TFDMemTable.Create(self); //new
RESTRequest1.Resource := ResourceName;
RESTRequest1.ResourceSuffix := '{qry}';
RESTRequest1.AddParameter('qry', TIdURi.ParamsEncode(Params), TRESTRequestParameterKind.pkURLSEGMENT, [poAutoCreated]);
RESTRequest1.Execute;
RESTResponseDataSetAdapter1.Active := true;
RESTRequest1.Params.Delete('qry');
RESTRequest1.ResourceSuffix :='';
end;
At server side, I have written a function which decodes the json and inserts the item data into the database.
So, to insert an item, i have call TItemsHelper.InsertItem by passing the Titem object i need to insert. This works. But the doubts I have are :
RESTRequest1.AddParameter('qry', TIdURi.ParamsEncode(Params), TRESTRequestParameterKind.pkURLSEGMENT, [poAutoCreated]);
is the way mentioned above the correct method to pass the encoded
json object to server (making it a part of the URL ) ? If not how
can i send json data to datasnap server ?
Can I get some advise on things I have done incorrectly here ?
Thanks in advance for your patience to go through this. Looking forward for some guidance.

If you have a DataSnap REST Server, there is a wizard to build DataSnap client application. The REST Client Library you use is for generic REST server. If you have DataSnap Server, and you want Delphi client application, you can take advantage to use wizard DataSnap REST Client Module.
BTW, Embarcadero now recommends RAD Server to develop REST server.

As #erwin Mentioned you should check the DataSnap REST Client Module. At least that's how I have set up my Proof of Concept application I made for a client. For example the DataSnap Server has a few REST Client Module which has a public method called GetListByFetchParams which returns a TFDJSONDataSets. The implementation looks a bit like this :
function TBaseServerMethods.InternalGetListByFetchParams(aFetchParams: TFetchParams): TFDJSONDataSets;
begin
{$IFDEF USE_CODESITE}
CSServerMethods.TraceMethod( Self, 'InternalGetListByFetchParams', tmoTiming );
CSServerMethods.Send( csmFetchParams, 'aFetchParams', aFetchParams );
{$ENDIF}
Result := TFDJSONDataSets.Create;
// Close the ListDataSet
ListDataSet.Close;
// Open the List DataSet
ListDataSet.Open;
// Add the ListDataSet to the result using a predefined name.
TFDJSONDataSetsWriter.ListAdd( Result, ListDataSetName, ListDataSet );
{$IFDEF USE_CODESITE}
CSServerMethods.Send( 'qryList.SQL', ListDataSet.SQL );
CSServerMethods.Send( csmFDDataSet, 'qryList DataSet Contents', ListDataSet );
{$ENDIF}
end;
The most important thing is that I create a TFDJSONDataSets which I set up as th result. I removed some code but in short the aFetchParams is used to alter the SQL Statement of the FireDaC Query component which is referenced by ListDataSet and it then Opens that dataset. Next I use a TFDJSONDataSetsWriter.ListAdd call to add our ListDataSet to the TFDJSONDataSets which is the result of the function.
Client Side I need some code too. This code will call the remote method on the DataSnapServer which will return the TFDJSONDataSets and which I can then deserialise into the TFDMemTables on the client :
procedure TBaseAgent.FetchListWithParams(aFetchParams : TFetchParams);
var
oListData : TFDJSONDataSets;
oKeyValues : TDictionary< String, String >;
begin
{$IFDEF USE_CODESITE}CSAgent.TraceMethod( Self, 'FetchListWithParams', tmoTiming );{$ENDIF}
oListData := FetchListWithParamsFromServer( aFetchParams );
try
tblList.AppendData( TFDJSONDataSetsReader.GetListValue( oListData, 0 ) );
tblList.Open;
finally
FreeAndNil( oListData );
end;
end;

Related

TRESTRequest JSON POST issue in Delphi

I'm trying to send JSON POST request to an API using a TRESTClient (Delphi 10.4/Windows 10), but I'm receiving a 409 conflict indicating "No input found". Using the same code in (Delphi XE6/Windows 10) I get a REST request failed: Socket Error # 10054 Connection reset by peer.. Using WireShark I was able to see that Delphi XE6 was sending the request with TLS 1.0 and Delphi 10.4 is using TLS v1.2. The API service accepts only TLS v1.2. Is there a way to use these REST components in Delphi XE6 to deliver the request with TLS 1.2?
I beleived my problem was that I was JSON encoding all the input with the code:
// Assign JSON
sl := TStringList.Create;
sl.Add('locations=[{"address":"Test1","lat":"52.05429","lng":"4.248618"},{"address":"Test2","lat":"52.076892","lng":"4.26975"},{"address":"Test3","lat":"51.669946","lng":"5.61852"},{"address":"Sint-Oedenrode, The Netherlands","lat":"51.589548","lng":"5.432482"}]');
and then assigning it to the Request Body with RESTRequest.AddBody(sl);, whereas the API only expects the value of the locations parameter to be JSON.
I have updated the code to assign the JSON to a String and used the RESTRequest.Params.Items[0].name := 'locations'; and RESTRequest.Params.Items[0].Value := s; to add the string to the request body.
Here's my updated code:
procedure TMainForm.btn_RouteXL_PostClick(Sender: TObject);
var
s, sResult, AError: String;
jsObj: TJSONObject;
AJSONValue, jsResponse: TJSONValue;
AParameter: TRESTRequestParameter;
AStatusCode: Integer;
begin
//ResetRESTComponentsToDefaults;
s := '[{"address":"Test1","lat":"52.05429","lng":"4.248618"},{"address":"Test2","lat":"52.076892","lng":"4.26975"},{"address":"Test3","lat":"51.669946","lng":"5.61852"},{"address":"Sint-Oedenrode, The Netherlands","lat":"51.589548","lng":"5.432482"}]';
RESTClient.BaseURL := 'https://api.routexl.com/tour';
RESTClient.ContentType := 'ctAPPLICATION_X_WWW_FORM_URLENCODED';
RESTClient.Authenticator := HTTPBasic_RouteXL;
RESTRequest.Method := TRESTRequestMethod.rmPOST;
RESTRequest.Params.AddItem; //Adds a new Parameter Item
RESTRequest.Params.Items[0].name := 'locations'; //sets the name of the parameter. In this case, since I need to use 'locations=' on the request, the parameter name is locations.
RESTRequest.Params.Items[0].Value := s; //Adds the value of the parameter, in this case, the JSON data.
RESTRequest.Params.Items[0].ContentType := ctAPPLICATION_X_WWW_FORM_URLENCODED; //sets the content type.
RESTRequest.Params.Items[0].Kind := pkGETorPOST; //sets the kind of request that will be executed.
RESTRequest.Execute;
try
memo_ResponseData.Lines.Add('************JSONText*******************');
memo_ResponseData.Lines.Add(RESTResponse.JSONValue.ToString);
//Memo1.Lines.Add('JSONValue :'+RESTResponse.JSONValue.Value);
memo_ResponseData.Lines.Add('StatusText :'+RESTRequest.Response.StatusText );
memo_ResponseData.Lines.Add('StatusCode :'+IntToStr(RESTRequest.Response.StatusCode ));
memo_ResponseData.Lines.Add('ErrorMesage:'+RESTRequest.Response.ErrorMessage);
memo_ResponseData.Lines.Add('************Content*******************');
memo_ResponseData.Lines.Add(RESTRequest.Response.Content);
memo_ResponseData.Lines.Add('************Headers*******************');
memo_ResponseData.Lines.Add(RESTRequest.Response.Headers.Text);
except
on E: Exception do
begin
memo_ResponseData.Lines.Add('************Error*******************');
memo_ResponseData.Lines.Add('Error : ' +E.Message);
memo_ResponseData.Lines.Add(E.ClassName);
memo_ResponseData.Lines.Add('************Error*******************');
memo_ResponseData.Lines.Add('Error : '+RESTResponse.Content);
end;
end;
end;
I'm able to get it to work in Postman using a key-value pair, by assigning the key as locations and the value as the contents of sl as seen in the screenshot. But I'm unable to translate that to Delphi.

How to declare a POST request in Delphi REST Datasnap?

I'm trying to write a function that will insert informations in my database using JSON or multiple parameters in the URL.
I already managed to create a function that returns informations from my database to the client but not the other way. Every example I find explains how to creat a JSON but not how to receive it with System.JSON (Delphi 10.2)
Here is a code sample for a GET function actually working on my Datasnap :
function TDM_Test.DS_getArticles(pStock : String): string;
begin
try
VGOutils.PR_logs('Get all articles from table');
VGOutils.FU_CheckConnect('1234567890');
with VGOutils.SQ_temp1 do
begin
SQL.Clear;
SQL.Add('SELECT code, name, price, seller, departement FROM articles');
SQL.Add('WHERE stock');
ParamByName('stock').AsString := pStock;
VGOutils.SQ_temp1.Open;
end;
GetInvocationMetadata().ResponseCode := 200;
GetInvocationMetadata().ResponseContent :=
VGOutils.PR_JSON(VGOutils.SQ_temp1, ['code', 'name', 'price', 'seller']);
except on E: Exception do
PR_error(E.Message);
end;
VGOutils.PR_Disconnect;
end;
Now I need the client to send me new articles to INSERT in my database.
The only thing that I cant figure out is how to declare the function and it's parameters.
I want the client to be able to send me a JSON with this correct format
{"article":[{"code":"AAA","name":"car","price" : "10400.90", "sellers" : [{"seller1" : "Automaniac", "seller2" : "Autopro" }]}]}
Now I know how to parse it with TJSONObject.ParseJSONValue() by reading this JSON RadStudio
EDIT
If this JSON string is hardcoded it works fine with ParseJSONValue but if the string is taken from the URL my JSONstring only contains "]}" and obviously there is nothing to parse.
function TDM_Test.DS_insertArticles(pUrlJsonString: String): string;
// Hardcoded JSON string
jsonString := '{"article":[{"code":"AAA","name":"car","price" : "10400.90", "sellers" : [{"seller1" : "Automaniac", "seller2" : "Autopro" }]}]}';
JSonValue := TJSONObject.ParseJSONValue(jsonString);
// String received in url
jsonString := pUrlJsonString;
JSonValue := TJSONObject.ParseJSONValue(pUrlJsonString);
I found the way I wanted it to work by not using the string parameter of the function but instead using the Custom body by passing a JSONObject.
The method is POST and the content type is application/JSON.
function TDM_Test.DS_insertArticles(pUrlJsonObj: TJSONObject): string;
JSonValue := TJSONObject.ParseJSONValue(pUrlJsonObj);
// Retrieve data for database fields
artCode := JSONValue.GetValue<string>('article[0].code');
artName := JSONValue.GetValue<string>('article[0].name');
artPrice := JSONValue.GetValue<string>('article[0].price');
//... Proceed to INSERT SQL with these values

I need to know where I'm wrong in json object to POST using TIdHTTP in Delphi

source gives socket error 14001, WITH OBJ JSON PARAM MESSAGE FOR POST
jso := TlkJSONobject.Create; // (data) as TlkJSONobject;
jso.Add('InvoiceNumber', '');
jso.Add('POSID', '910441');
jso.add('USIN', ePOSNo.Text);
jso.add('DATETIME', eDate.Text);
IdHTTP1.Request.Accept := 'application/json';
IdHTTP1.Request.ContentType := 'application/json';
{ Call the Post method of TIdHTTP and read the result into TMemo }
Memo1.Lines.Text := IdHTTP1.Post('http://localhost;8524/api/IMSFISCAL/GetInvoiceNumberByModel', JSO);
json cannot be passed as tstream
need help on it
There is no way the code you showed can produce a socket error (let alone error 14001, which is not even a socket error) since the code won't even compile!
The TIdHTTP.Post() method does not have an overload that accepts a TlkJSONobject as input. How could it? TlkJSONobject comes from a completely different 3rd party library, it is not part of the RTL or Indy. The only things you can POST with TIdHTTP are:
TStrings-derived types
TStream-derived types, including Indy's TIdMultiPartFormDataStream
a file specified by a String filename
In this case, you need to use a TStream to post JSON stored in memory. It is your responsibility to save your TlkJSONobject content to a suitable TStream of your choosing. That is outside the scope of Indy. For instance, you can use TlkJSON.GenerateText() to get the JSON into a String and then POST it using a TStringStream.
On a side note, the URL you are passing to TIdHTTP.Post() is malformed. The correct delimiter between a hostname and port number is a colon (:), not a semicolon (;).
With that said, try this:
jso := TlkJSONobject.Create;
jso.Add('InvoiceNumber', '');
jso.Add('POSID', '910441');
jso.add('USIN', ePOSNo.Text);
jso.add('DATETIME', eDate.Text);
IdHTTP1.Request.Accept := 'application/json';
IdHTTP1.Request.ContentType := 'application/json';
{ Call the Post method of TIdHTTP and read the result into TMemo }
PostData := TStringStream.Create(TlkJSON.GenerateText(jso), TEncoding.UTF8);
try
Memo1.Lines.Text := IdHTTP1.Post('http://localhost:8524/api/IMSFISCAL/GetInvoiceNumberByModel', PostData);
finally
PostData.Free;
end;

Delphi HMRC test-api.service.hmrc.gov.uk/hello/world

I'm trying the HMRC Developers HUB tutorials from:
https://developer.service.hmrc.gov.uk/api-documentation/docs/tutorials
I have tried two ways of the "Hello World", but keep getting:
{"code":"ACCEPT_HEADER_INVALID","message":"The accept header is missing or invalid"}
Example 1 REST Client:
procedure TForm1.btnTest_REST_ClientClick(Sender: TObject);
var
jValue: TJSONValue;
begin
RESTClient1.BaseURL := cbHMRC_Test_URLs.Text;
RESTRequest1.Execute;
jValue := RESTResponse1.JSONValue;
MemoContent.Text:= jValue.ToString;
end;
Example 2 TdHTTP:
procedure TForm1.btnTest_HTTPClick(Sender: TObject);
var
get_url: string;
resp: TMemoryStream;
begin
get_url := 'https://test-api.service.hmrc.gov.uk/hello/world';
resp := TMemoryStream.Create;
try
IdHttp1.Request.CustomHeaders.AddValue('Accept', 'application/vnd.hmrc.1.0+json');
IdHTTP1.Get(get_url, resp);
resp.Position := 0; // <-- add this!!
MemoContent.Lines.LoadFromStream(resp);
finally
resp.Free;
end;
end;
Both make the connection, but fail on the Header.
Any ideas about what I'm doing wrong?
I would suggest going with the REST components. I've used them significantly and they work quite well.
On the REST component side, your just missing the Request Accept value:
RESTRequest1.Accept := 'application/vnd.hmrc.1.0+json';
I tested your example to their hello world resource and received:
{"message":"Hello World"}
Looks like it's working.
For those of you struggling like me with how to implement the initial HMRC tutorials in Delphi, try the following.
Create a new application. I chose a Multi-Device / Blank Application option.
On the main form, add the following components:-
TRESTClient
TRESTRequest
TRESTResponse
TMemo
TButton
Add the System.JSON unit to the uses clause.
Set the Button1Click procedure as follows:-
procedure TForm1.Button1Click(Sender: TObject);
var
jValue: TJSONValue;
begin
RESTClient1.BaseURL := 'https://test-api.service.hmrc.gov.uk/hello/world';
RESTRequest1.Accept := 'application/vnd.hmrc.1.0+json';
RESTRequest1.Execute;
jValue := RESTResponse1.JSONValue;
Memo1.Text:= jValue.ToString;
end;
Run the program, click the button and voila!
I hope this helps someone

How to deliver a JSON object from Oracle 10 to a web service in the cloud

I have been tasked with providing a solution to deliver a JSON object from Oracle 10 to a web service in the cloud. I will be using PL/JSON for generating the JSON object, the problem comes with the delivery aspect of the required solution. Is there functionality within Oracle using PL/SQL for deliver of the JSON object to the cloud (this is very much my preferred approach)? Or will I need to resort to an external service in something like Java to provide this functionality? Some sample code/psuedo code would be much appreciated.
PS: I have very little exposure to web-technologies so please be gentle :-)
Regards
Paul J.
you need use utl_http package
here's a good example of how to post json string from pl/sql to web service
invoke-a-rest-service-from-plsql
begin
publish_cinema_event('2', -4);
end;
create or replace
procedure publish_cinema_event
( p_room_id in varchar2
, p_party_size in number
) is
req utl_http.req;
res utl_http.resp;
url varchar2(4000) := 'http://localhost:9002/cinema';
name varchar2(4000);
buffer varchar2(4000);
content varchar2(4000) := '{"room":"'||p_room_id||'", "partySize":"'||p_party_Size||'"}';
begin
req := utl_http.begin_request(url, 'POST',' HTTP/1.1');
utl_http.set_header(req, 'user-agent', 'mozilla/4.0');
utl_http.set_header(req, 'content-type', 'application/json');
utl_http.set_header(req, 'Content-Length', length(content));
utl_http.write_text(req, content);
res := utl_http.get_response(req);
-- process the response from the HTTP call
begin
loop
utl_http.read_line(res, buffer);
dbms_output.put_line(buffer);
end loop;
utl_http.end_response(res);
exception
when utl_http.end_of_body
then
utl_http.end_response(res);
end;
end publish_cinema_event;