Delphi TRESTRequest gets Access Violation when attempting TRESTRequest.AddBody() - json

I am working with a Delphi (10.2.2) Client app that calls API functions on a Delphi REST Server (WebModule). At this point I have 10s of these types of API calls that perform all relevant HTTP verbs (POST, GET, PUT, DELETE) and they all work fine, EXCEPT for calls where I need to upload a JSON object for INSERT/EDITs on the Server. I am not sure what I am doing wrong, so if anyone could look at my attempt and let me know what I'm missing?
var
REQ : TRESTRequest;
RESP : TRESTResponse;
NewPL : TJSONObject;
strOut : String;
begin
try
REQ := TRESTRequest.Create(Application);
RESP := TRESTResponse.Create(Application);
REQ.Response := RESP;
REQ.Resource := 'packlist';
REQ.Client := DM1.RESTClient;
REQ.Method := TRESTRequestMethod.rmPOST;
//Create JSON from UI
NewPL := TJSONObject.Create;
NewPL.AddPair('PL_NO', edtPLNo.Text);
NewPL.AddPair('PL_DATE', edtPLDate.Text);
NewPL.AddPair('SHIP_NO', edtShipmentNo.Text);
NewPL.AddPair('SHIPPER', memShipper.Text);
NewPL.AddPair('CONSIGNEE', memConsignee.Text);
NewPL.AddPair('VEHICLE_REG_NO', edtVehicleRegNo.Text);
NewPL.AddPair('ETD', DateTimeToStr(dateETD.Date));
NewPL.AddPair('ETA', DateTimeToStr(dateETA.Date));
NewPL.AddPair('TOTAL_QTY', edtTotalQty.Text);
NewPL.AddPair('TOTAL_WEIGHT', edtTotalWeight.Text);
NewPL.AddPair('TOTAL_VOLUME', edtTotalVol.Text);
if chkPLComplete.Checked then
NewPL.AddPair('COMPLETE', '1')
else
NewPL.AddPair('COMPLETE', '0');
NewPL.AddPair('SUPPLIER_NO', edtSuppNo5.Text);
NewPL.AddPair('DEBTOR_ACC_NO', '');
NewPL.AddPair('WAYBILL_NO', edtWaybillNo.Text);
strOut := NewPL.ToString;
REQ.AddBody(NewPL); //AV occurs here
REQ.Execute;
DisplayPL(NewPL);
finally
REQ.DisposeOf;
RESP.DisposeOf;
end;
end;
I am sure I have made a mistake somewhere, that I keep overlooking...

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.

DELPHI - Get JSON POST Request for debugging

I am new to REST in Delphi and I am wanting to view the content as text that is about to be posted. When attempting to use TJson.ObjectToJsonString I get a Stack Overflow error.
The code I am using is:
var
RestClient: TRestClient;
RestRequest: TRestRequest;
Param: TRESTRequestParameter;
begin
Memo1.lines.clear;
RestClient := TRestClient.create(nil);
RestRequest := TRestRequest.create(nil);
RestClient.Params.clear;
RestRequest.Client := RestClient;
RestRequest.Params.clear;
RestRequest.ClearBody;
RestRequest.Method := TRESTRequestMethod.rmPOST;
RestClient.BaseURL := 'http://my.server/MyService';
RestClient.ContentType := 'application/json';
Param := RestClient.Params.AddItem('my-webservice-password', 'MySecret');
Param.Kind := pkHTTPHEADER;
RestClient.Params.AddItem('Param1', 'MyFistParam');
RestClient.Params.AddItem('Param2', 'MySecondParam');
RestClient.Params.AddItem('3rdParam', 'true');
Memo1.lines.text := TJson.ObjectToJsonString(RestClient); // <<<<< Stack Overflow here
RestRequest.Execute;
end;
Can someone please advise why this would be creating a stack overflow, or alternatively what I should be doing to view the JSON request that is about to be submitted?
Thanks & Regards
Adam
Try using REST Debugger from tools menu . it is easier and show the results directly .

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;

TIdHttp returns JSON, TWebBrowser returns xml from Tableau server request

On to more curious challenges in the Delphi/Indy/TWebBrowser/Tableau saga.
I'm having a very hard time understanding why sending a GET request to the Tableau server utilizing the TIdHttp component always returns a JSON response, while the same request in a TWebBrowser control with the same auth header returns an XML response.
I slightly prefer XML - even though it is more bandwidth intensive - since we have much XML infrastructure and currently no JSON infrastructure.
When I send the request with the TIdHttp component, I'm sending it with the following params:
http.Request.CustomHeaders.Text := GetAuthHeader(FToken);
http.Request.ContentType := 'application/json';
I've also tried this:
http.Request.ContentType := 'text/xml';
http.Request.Accept := 'text/xml';
And
http.Request.ContentType := 'application/xml';
http.Request.Accept := application/xml';
And
<no settings specified for accept and contenttype, just let it use the defaults>
...
sHTML := http.Get('http://<myserver>/api/3.0/sites/' + FSiteId + '/views')
...and always receive back the response in JSON.
When I send the same request with TWebBrowser:
var
hdr,flags,targetframe,postdata,Aurl: OleVariant;
begin
AUrl := http://<myserver>/api/3.0/sites/' + FSiteId + '/views';
flags := navNoHistory+navNoReadFromCache+navNoWriteToCache;
targetframe := 1;
postdata := 1;
hdr := GetAuthHeader(FToken);
Navigate2(Aurl,flags,targetframe,postdata,hdr);
end;
...the response always comes back as XML.
Does anyone understand why this would occur? I've tried logging the raw request to see how it's being set up by TWebBrowser, but can't seem to get the raw request. Perhaps I should set up a proxy server...
TIA
Solved - had a
http.Request.ContentType := 'application/json...';
and a
http.Request.Accept := 'application/json...';
still hanging around from the form create event.

When refactoring my datasnap server cannot marshall if i moved a class to a different unit

I have a datasnap server that I have to keep running or at least cannot update.
I would like to connect to it with a new Client.
All the code is the same but I refactored some code so now a class I use to connect to the server is in another unit.
The servermethods is a function that returns TMyObject. But in reality the server returns a marshalled object of type Oldunit.TMyObject. But this is not recognized by the client.
function TJSONUnMarshal.CreateObject(JsonObj: TJSONObject): TObject;
var
objType: string;
ObjId: string;
objFields: TJSONObject;
Obj: TObject;
rttiType : TRttiType;
attr : TCustomAttribute;
customizer : TJSONPopulationCustomizer;
JsonPairID: TJSONPair;
JsonPairType: TJSONPair;
JsonPairFields: TJSONPair;
JsonPairRefName: TJSONPair;
begin
assert(JsonObj <> nil);
assert(JsonObj.Count > 1);
JsonPairID := JsonObj.Get(ID_NAME);
if JsonPairID <> nil then
begin
JsonPairType := JsonObj.Get(TYPE_NAME);
JsonPairFields := JsonObj.Get(FIELDS_NAME);
Assert(JsonPairFields <> nil);
Assert(JsonPairType <> nil);
objType := JsonPairType.JsonValue.Value;
ObjId := JsonPairID.JsonValue.Value;
objFields := TJSONObject(JsonPairFields.JsonValue);
Obj := ObjectInstance(FRTTICtx, objType); // returns nil, objtype is wrong
if Obj = nil then
raise EConversionError.Create(Format(SCannotCreateType, [objType]));
In My Servermethodsclient I changed the Type of my parameter. It works but it looks more like a quick fix to me and i wish i could automate it. If i had plenty of servermethods like this this would be tedious work, but for now it is only one method to authenticate on a server.
pJSON := TJSONObject( FPW_GetServerInfoCommand.Parameters[2].Value.GetJSONValue(True));
pPair := pJSON.RemovePair('type');
pPair.Free;
pJSON.AddPair(TJSONPair.Create('type', 'NewUnit.TMyObject'));
Result := TMyObject(FUnMarshal.UnMarshal(FPW_GetServerInfoCommand.Parameters[2].Value.GetJSONValue(True)));