Sending a JSON encoded object using Indy and Delphi
Solution 1
I think I solved it. I am not sure this is the most beautiful way of doing this, but it seems to do what I want.
Here's the code!
procedure TIndyLoginServer.SendRequest(Command, Json: string;
Request: TRequest);
var
JsonToSend: TIdStringStream;
ServerResponse: string;
Url: string;
begin
// Build parameters
JsonToSend := TIdStringStream.Create(Json);
try
try
FIndyHttp.Request.Accept := 'application/json';
FIndyHttp.Request.ContentType := 'application/json';
FIndyHttp.Request.ContentEncoding := 'utf-8';
Url := FUrl +'?command=' + Command;
ServerResponse := FIndyHttp.Post(Url, JsonToSend);
Request.OnRequestFinished(ServerResponse, '', '');
except
on E: EIdHTTPProtocolException do
begin
Request.OnRequestFinished('', E.Message, '');
end;
on E: EIdSocketError do
begin
if E.LastError = Id_WSAETIMEDOUT then
Request.OnRequestTimedOut();
end;
on E: EIdException do
begin
Request.OnRequestFinished('', E.Message, '');
end;
end;
finally
JsonToSend.Free();
end;
end;
Solution 2
Mostly a hint: When you use "application/x-www-form-urlencoded" the server side expects to see key/value pairs like:
key1=value1&key2=value2 etc.
In this case you can send JSON text as a value of a key, for example:
{...}
Params.Add('command=' + Command);
Params.Add('jsonText=' + json);
FIndyHttp.Request.ContentType := 'application/x-www-form-urlencoded';
ServerResponse := FIndyHttp.Post(FUrl, Params);
{...}
Using "application/json" you say to the HTTP server: the whole body of the request (the part after headers) will contain JSON text.
In PHP I have access to the "body" of the request using "input stream":
$jsonText = file_get_contents("php://input")
$jsonObject = json_decode($jsonText )
Try finding something similar in JAVA?
UPDATE:
It seems TIdStrings prepares the request to be 'application/x-www-form-urlencoded'. As Remy Lebeau suggests here save your JSON string to a TStream descendant (tMemoryStream for example) and transfer it as is (i.e. RAW). tIdHttp.POST has an override that accepts TStream. In this case try TIdHTTP.Request.ContentType = 'application/json'. It should work that way?
Related videos on Youtube
kling
Updated on June 29, 2022Comments
-
kling almost 2 years
I've been working on this problem for far too many hours now without really making any headway. I have an old solution which works, but I am trying to port it to Indy to make the code a bit more reliable and easier to maintain.
We have a servlet which handles requests sent to it using HTTP POST messages. The messages have a single parameter, "command", the value of which determines what the servlet should do.
Currently I have this:
procedure TIndyLoginServer.SendRequest(Command, Json: string; Request: TRequest); var Params: TIdStrings; ServerResponse: string; begin // Build parameters Params := TStringList.Create(); Params.Add('command=' + Command); try // Content type should really be 'application/json' but then the parameters stop working FIndyHttp.Request.ContentType := 'application/x-www-form-urlencoded'; ServerResponse := FIndyHttp.Post(FUrl, Params); Request.OnRequestFinished(ServerResponse, '', ''); except on E: EIdHTTPProtocolException do begin Request.OnRequestFinished('', E.Message, ''); end; on E: EIdSocketError do begin if E.LastError = Id_WSAETIMEDOUT then Request.OnRequestTimedOut(); end; on E: EIdException do begin Request.OnRequestFinished('', E.Message, ''); end; end; end;
This sort of works, the command gets to the servlet and it starts working as expected. The problem is, in addition to the parameters I need to be able to send a JSON encoded object along with the POST request. The Java servlet receives the JSON encoded object using the following line of code
final BufferedReader r = req.getReader();
The "req" is obviously the incoming POST request and the buffered reader is later used to decode the object. I can't for the life of me figure out how to attach the JSON string to the TidHTTP instance in a way that the servlet can read the data, however.
Does anyone have any suggestions or examples I can look at? All I have found is how to send a file. Maybe that is what I am looking for?
And how can I set the content type of the request to 'application/json' without breaking the parameter list? If I change it, the POST request still reaches the server but the "command" parameter is no longer found.
-
kling about 11 yearsThanks for your reply, but I am trying to avoid changing the servlet code since we have other applications which use it and I would prefer not having to modify those. It would be best if I could figure out how to attach the JSON object along with the parameters in Delphi :)
-
iPath ツ about 11 years@kling take a look at the update I've made
-
kling about 11 yearsThanks, but no dice. The command parameter still vanishes even when I use a TIdStringStream instead of the TStringList, unless I set FIndyHttp.Request.ContentType to "application/x-www-form-urlencoded". :/
-
mjn about 11 yearsso the Url has to contain the command parameter? The code in your question was looking different, and worked, which is strange ;)
-
iPath ツ about 11 yearsHmm, I missed two things: command is in the QueryString and of course the encoding :) Newer Delphi versions internally use UTF16LE and may be (but not sure) Indy defaults to Unicode... Glad to see it working :)
-
iPath ツ about 11 years@mjn ServletRequest.getParameter(...) - if they use it - combines parameters from GET and POST, similarly to PHP's $_REQUEST array. I also was misleaded by the Code in the Question, because application/x-www-form-urlencoded sounds like POST
-
kling about 11 yearsIt is a POST so it wasn't misleading. Confusing maybe. You can send URL-encoded parameters in a POST message as well, along with the data of the actual post. What Indy did in the first example was it recognized the Parameters parameter to FIndyHttp.Post was in fact the parameters of the message, not the data, and encoded them automatically in the URL. But that left no room for the actual POST data to be sent as well.