Sending a JSON encoded object using Indy and Delphi

10,494

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?

Share:
10,494

Related videos on Youtube

kling
Author by

kling

Updated on June 29, 2022

Comments

  • kling
    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
    kling about 11 years
    Thanks 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 ツ
    iPath ツ about 11 years
    @kling take a look at the update I've made
  • kling
    kling about 11 years
    Thanks, 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
    mjn about 11 years
    so the Url has to contain the command parameter? The code in your question was looking different, and worked, which is strange ;)
  • iPath ツ
    iPath ツ about 11 years
    Hmm, 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 ツ
    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
    kling about 11 years
    It 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.