1

I am trying to access a REST server that has an endpoint that uses the GET verb while it also requires json data to be sent in the body

I am trying to accomplish that with an TIdHttp class, using the Get method.

Right now I create a TStringStream with the json data, and then assign that stream to the Request.Source property of the TIdHttp object.

The server however responds with an error code indicating it did not receive the body.

How do you send a body with GET request using TIdHttp?

Remy Lebeau
  • 454,445
  • 28
  • 366
  • 620
R. Beiboer
  • 692
  • 7
  • 20
  • 1
    Whoever designed that server made a huge mistake. While HTTP standards technically do allow a body in a `GET`, it's not *officially* supported. It seems the `TIdHTTP` simply ignores the body in `GET` requests. Read more: https://stackoverflow.com/questions/978061/http-get-with-request-body – Jerry Dodge Jan 23 '19 at 15:13
  • 1
    Long story short, your options are either A) Hack the `TIdHTTP` to make it send the body, or B) Find another HTTP component which supports this (which is not likely). – Jerry Dodge Jan 23 '19 at 15:17
  • 2
    @JerryDodge "Whoever designed that server paid more attention to the documented standard than the de facto standards as widely implemented". There, I fixed it for ya. We encountered the exact same problem when I worked at a company building integration middleware - you'd be surprised how many companies have implemented REST Api's that [gasp] follow the HTTP standards when it comes to Api end point behaviours, rather than following the _browser behaviour_ crowd. We simply modified our Indy code to make it possible. :shrug: :) – Deltics Jan 23 '19 at 18:19

2 Answers2

4

As Remy suggests, you could hack an 'accessor' class to contrive to access this method via a type-coerced call.

Or, you could simply sub-class TIdHttp and add an appropriate, new Get() convenience method, following the patterns established for all of the other such methods.

e.g. something like:

interface

  type
    TOurHttp = class(TIdHttp)
    public
      procedure Get(aUrl: String; aRequestBody, aResponseContent: TStream);
    end;

implementation

  procedure TOurHttp.Get(aUrl: String; aRequestBody, aResponseContent: TStream);
  begin
    DoRequest(Id_HttpMethodGet, aUrl, aRequestBody, aResponseContent, []);
  end;

This is what we did at a company I worked at previously, building integration/middleware software, as there were many examples where the provided Indy convenience methods (at the time) did not cover all of our use cases, not just GET, especially when it came to REST Api's. Note that the above is not our actual code from that time, I no longer have access to that.

Instead of using the TIdHttp class directly we used TOurHttp (not the real name), bestowed with numerous additional convenience methods useful to us. We also found we had to make changes to and extend aspects of other things such as the IgnoreReplies behaviour, to accommodate various 'rogue' REST Api server behaviours (not all of which were HTTP standards compliant) which again you would not find in the majority of use cases.

Of course, instead of fixing your client behaviour, you could try asking the provider of the Api or HTTP server to change their end. Good luck with that. ;)

Background Reading

The HTTP protocol specification does allow for a GET request to submit a request body, but the vast majority of implementations do not (or simply ignore any such content). This is perhaps because historically the vast majority of implementations have targeted - or been driven by - browser behaviours, combined with the fact that the HTTP specification has unfortunately allowed some "wriggle room" in this area.

This has resulted in where we are today when, at time of writing (January 2019) the relevant part of the HTTP spec RFC7231 has this to say about content in a GET request:

A payload within a GET request message has no defined semantics; sending a payload body on a GET request might cause some existing implementations to reject the request.

i.e. request content is allowed, since it is not explicitly forbidden and its use is acknowledged as possible and sometimes expected (and only sometimes rejected). But you cannot rely on a server adopting any particular interpretation (or even acceptance) of such a request.

That of course doesn't help a client dealing with a particular server that has decided on its particular interpretation. By conceding to bad practice in this way this iteration of the HTTP specification is less than useful; it simply describes the variance in practice rather than providing a specification against which an implementation may be tested for compliance/accuracy.

Deltics
  • 20,843
  • 2
  • 38
  • 67
2

Assigning the TIdHTTP.Request.Source property will not work because the Source will get replaced with nil when TIdHTTP.Get() calls TIdHTTP.DoRequest() internally passing nil for its ASource parameter.

So, to do what you are asking, you will have to call DoRequest() directly. However, it is a protected method, so you will have to use an accessor class to reach it.

For example:

type
  TIdHTTPAccess = class(TIdHTTP)
  end;

var
  QueryData, ReplyData: TStream;
begin
  QueryData := TStringStream.Create('... json data here...', TEncoding.UTF8);
  try
    IdHTTP1.Request.ContentType := 'application/json';
    IdHTTP1.Request.CharSet := 'utf-8';

    //Result := IdHTTP1.Get('http://...');
    ReplyData := TMemoryStream.Create;
    try
      TIdHTTPAccess(IdHTTP1).DoRequest(Id_HTTPMethodGet, 'http://...', QueryData, ReplyData, []);
      ReplyData.Position := 0;
      Result := ReadStringAsCharset(ReplyData, IdHTTP1.Response.Charset);
    finally
      ReplyData.Free;
    end;
  finally
    QueryData.Free;
  end;
end;
Remy Lebeau
  • 454,445
  • 28
  • 366
  • 620
  • Thanks Remy for the detailed explanation. I was wondering if TIdHttp did not support this out of the box, or if I was doing something wrong. It turns out it is not supported out of the box, for obvious reasons I think. Your suggestion, and also @Deltics 's suggestion will perfectly help me. – R. Beiboer Jan 24 '19 at 14:37