IdHttp : how to deal with json request and response - json

I have encountered a few sites that uses JSON for request and response
I come across two types :
1- application/x-www-form-urlencoded as request and return a response application/json content type
2- application/json content type for both request and response
in type 1 i tried changing the the response content type using
mIdHttp.Response.ContentType := 'application/json';
but using http analyzer i can see that it doesn't change and its still text/html
now i do not know if the problem is with the fact that i can't change content type or not but i do not know how to deal with json !
a few question regarding json :
1- do i have to encode json data when posting ? how ?
2- how can i parse the json response code ? how to get it ? does it need some kind of encode or special convertion ?
3- what kind of idhttp setting for json changes with each site and needs configuring ?
I understand my questions sound a bit general, but all the other questions are very specific and don't explain the basics when dealing with 'application/json' content type.
Edit 1 :
thanks to Remy Lebeau answer i was able to successfully work with type 1
but i still have trouble sending JSON request, can someone please share an working example, this is one of the sites posting information please use this for your example :
One important note : this particular site's post and request content are exactly like each other ! and it baffles me because at the site, i specify an start date and an end date then click on a folder like icon and this post is sent (the one you can see above), and the result should be links (and it is) but instead of appearing only in request content they also appear in post ! (also i'm trying to get the links but at the post the links, the thing i want, are also being sent, how can i post something i don't have !!?)
just for more clarity here is the place i fill the date and the icon i mentioned :

You cannot specify the format of the response, unless the requested resource offers an explicit input parameter or dedicated URL for that exact purpose (ie, to request a response be sent as html, xml, json, etc). Setting the TIdHTTP.Response.ContentType property is useless. It will be overwritten by the actual Content-Type header of the response.
To send JSON in a request, you must post it as a TStream, like TMemoryStream or TStringStream, and set the TIdHTTP.Request.ContentType as needed, eg:
var
ReqJson: TStringStream;
begin
ReqJson := TStringStream.Create('json content here', TEncoding.UTF8);
try
IdHTTP1.Request.ContentType := 'application/json';
IdHTTP1.Post(URL, ReqJson);
finally
ReqJson.Free;
end;
end;
To receive JSON, TIdHTTP can either
return it as a String (decoded using a server-reported charset):
var
ReqJson: TStringStream;
RespJson: String;
begin
ReqJson := TStringStream.Create('json content here', TEncoding.UTF8);
try
IdHTTP1.Request.ContentType := 'application/json';
RespJson := IdHTTP1.Post(URL, ReqJson);
finally
ReqJson.Free;
end;
// use RespJson as needed...
end;
write the raw bytes to an output TStream of your choosing:
var
ReqJson: TStringStream;
RespJson: TMemoryStream;
begin
RespJson := TMemoryStream.Create;
try
ReqJson := TStringStream.Create('json content here', TEncoding.UTF8);
try
IdHTTP1.Request.ContentType := 'application/json';
RespJson := IdHTTP1.Post(URL, ReqJson, RespJson);
finally
ReqJson.Free;
end;
RespJson.Position := 0;
// use RespJson as needed...
finally
RespJson.Free;
end;
end;
The HTTP response code is available in the TIdHTTP.Response.ResponseCode (and TIdHTTP.ResponseCode) property.

Related

Delphi HTTPClient root page

I use THTTPClient instead of Indy to get a Json value from my API. I do it like this :
Url := 'http://example.com/api/index.php?get&latitude=' +
TNetEncoding.Base64.Encode(MapView.Location.Latitude.ToString)
+ '&longitude=' + TNetEncoding.Base64.Encode(MapView.Location.Longitude.ToString);
Json := TToolHTTP.Get(Url);
The method to get is write below :
class function TToolHTTP.Get(aUrl: string): string;
var
Client : THTTPClient;
Response: IHTTPResponse;
begin
Result := '';
Client := THTTPClient.Create;
try
try
Response := Client.Get(aUrl);
Result := Response.ContentAsString;
except
ShowMessage('Error');
end;
finally
FreeAndNil(Client);
end;
end;
The first time or two first time I get my Json value, the third time I call this method with same url I get the content of the root page (http://example.com/index.html), and then when I call it the 4 time I get my Json. It's look like really random, but for sure I always get a Status Code HTTP 200.
Why sometime I get the html of the root page ?

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.

TRESTRequest: How to add an array as the body of a PUT request

I want to send a PUT request where the body contains an array of JSON objects, like this:
PUT http://hostname/api/items
[{"ID":1},{"ID":2},...]
Using code like the following, I can easily send a POST request with a single TJSONObject in the body:
req := TRESTRequest.Create(nil);
req.Client := FRESTClient;
req.Method := TRESTRequestMethod.rmPOST;
req.Resource := 'api/items';
req.AddBody(someJSONObject);
req.Execute;
Fiddler shows the request as having the correct content:
{"ID",1}
However, if I use a PUT request and add a TJSONArray as the body instead...
ja := TJSONArray.Create;
for jo in someJSONObjects do
ja.Add(jo);
req.Method := TRESTRequestMethod.rmPUT;
req.AddBody(ja);
Fiddler shows the request as having a huge pile of bizarre JSON content:
{"elements":{"items":[{"members":{"items":[{"jsonString":{"strBuffer":{"data":["I","D","","","","","","","","","","","","","",""],"length":2,"maxCapacity":2147483647},"owned":true},"jsonValue":{"strBuffer":{"data":["1","","","","","","","","","","","","","","",""],"length":1,"maxCapacity":2147483647},"owned":true},"owned":true}],...
It looks like some kind of low-level serialization of the raw in-memory object, instead of the expected JSON array contents.
Any idea what I'm doing wrong? The documentation on the AddBody method is not very helpful.
Answering my own question...
The overloads of the AddBody method include:
procedure AddBody(AObject: TJSONObject);
procedure AddBody<T>(AObject: T);
I had assumed TJSONArray was derived from TJSONObject and would therefore use the first overload, but in fact both classes derive from TJSONValue. Therefore, the TJSONObject overload was not used in my case, but rather the generic overload, which apparently succeeded at some kind of lower-level serialization.
Since there is no direct overload for TJSONArray, and the API I'm using doesn't expect a JSON array wrapped in an object, I did this instead:
req.AddBody(ja.ToJSON, ctAPPLICATION_JSON);
This serializes the array to a string, and then specifies the content type as application/json.

Does the ulkJSON library have limitations when dealing with base64 in Delphi 7?

I'm working on a project that is using Delphi 7 to consume RESTful services. We are creating and decoding JSON with the ulkJSON library. Up to this point I've been able to successfully build and send JSON containing a base64 string that exceed 5,160kb. I can verify that the base64 is being received by the services and verify the integrity of the base64 once its there. In addition to sending, I can also receive and successfully decode JSON with a smaller (~ 256KB or less) base64.
However I am experiencing some issues on the return trip when larger (~1,024KB+) base64 is involved for some reason. Specifically when attempting to use the following JSON format and function combination:
JSON:
{
"message" : "/9j/4AAQSkZJRgABAQEAYABgAAD...."
}
Function:
function checkResults(JSONFormattedString: String): String;
var
jsonObject : TlkJSONObject;
iteration : Integer;
i : Integer;
x : Integer;
begin
jsonObject := TlkJSONobject.Create;
// Validate that the JSONFormatted string is not empty.
// If it is empty, inform the user/programmer, and exit from this routine.
if JSONFormattedString = '' then
begin
result := 'Error: JSON returned is Null';
jsonObject.Free;
exit;
end;
// Now that we can validate that this string is not empty, we are going to
// assume that the string is a JSONFormatted string and attempt to parse it.
//
// If the string is not a valid JSON object (such as an http status code)
// throw an exception informing the user/programmer that an unexpected value
// has been passed. And exit from this routine.
try
jsonObject := TlkJSON.ParseText(JSONFormattedString) as TlkJSONobject;
except
on e:Exception do
begin
result := 'Error: No JSON was received from web services';
jsonObject.Free;
exit;
end;
end;
// Now that the object has been parsed, lets check the contents.
try
result := jsonObject.Field['message'].value;
jsonObject.Free;
exit;
except
on e:Exception do
begin
result := 'Error: No Message received from Web Services '+e.message;
jsonObject.Free;
exit;
end;
end;
end;
As mentioned above when using the above function, I am able to get small (256KB and less) base64 strings out of the 'message' field of a JSON object. But for some reason if the received JSON is larger than say 1,024kb the following line seems to just stop in its tracks:
jsonObject := TlkJSON.ParseText(JSONFormattedString) as TlkJSONobject;
No errors, no results. Following the debugger, I can go into the library, and see that the JSON string being passed is not considered to be JSON despite being in the format listed above. The only difference I can find between calls that work as expected and calls that do not work as expect appears to be the size of base64 being transmitted.
Am I missing something completely obvious and should be shot for my code implementation (very possible)? Have I missed some notation regarding the limitations of the ulkJSON library? Any input would be extremely helpful. Thanks in advance stack!
So after investigating this for hours over the course of some time, I did discover that the library indeed was working properly and there was no issue.
The issue came down to the performance of my machine as it was taking on average 215802 milliseconds (3.5967 minutes) to process a moderately sized image (1.2 meg) in base64 format. This performance scaled according to the size of the base64 string (faster for smaller, longer for larger).