3
votes

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.

2

2 Answers

3
votes

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?

2
votes

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;