TRestClient/TRestRequest incorrectly decodes gzip response

假如想象 提交于 2019-11-30 20:25:08
GolezTrol

Remy Lebeau's input in his answer to this question as well as his comment to the answer in the question Automatically Decode GZIP In TRESTResponse? put me on the right track.

Like he said, setting AcceptEncoding doesn't suffice, because the TIdHTTP that performs the actual request doesn't have a decompressor attached, so it can't decompress the gzip response. Based on the sparse resources, I got the idea that setting AcceptEncoding would automatically decompress the response too, but that idea was wrong.

Still, leaving AcceptEncoding empty doesn't work either in this case, since the API this is all about, which is the StackExchange API, is always compressed, regardless whether you specify that you accept gzip or not.

So the combination of a) an always compressed response, b) an HTTP client that cannot decompress and c) a TRESTRequest object that -incorrectly- assumed that the response is already properly decompressed together lead to this situation.

I see only two solutions, the first being to discard TRESTClient altogether and just perform the request with a plain TIdHTTP. A pity, since my goal was to explore the possibilities of the new REST components to see how they can make life easier.

So the other solution is to assign a compressor to the TIdHTTP that is used internally.

I managed to succeed, although unfortunately it undoes a lot of the abstraction that the TREST components are trying to introduce. This is the code that solves it:

var
  Http: TIdCustomHTTP;
begin
  // Get the TIdHTTP that performs the request.
  Http := (RESTRequest1 // The TRESTRequest object
    .Client // The TRESTClient
    .HTTPClient // A TRESTHTTP object that wraps HTTP communication
    .Peer // An IIPHTTP interface which is obtained through PeerFactory.CreatePeer
    .GetObject // A method to get the object instance of the interface
    as TIdCustomHTTP // The object instance, which is an TIdCustomHTTP.
  );

  // Attach a gzip decompressor to it.
  Http.Compressor := TIdCompressorZLib.Create(Http);

After this, I can use the RESTRequest1 component to successfully fetch the JSON response (at least as text).

Remy Lebeau

AcceptEncoding = 'gzip, deflate'

This is the root of your problem. You are manually telling the server that the response is allowed to be gzip encoded, but as far as I can see in the REST source code, the underlying TIdHTTP object that TRESTClient uses internally does not have a gzip decompressor assigned to it (even if it had one, assigning AcceptEncoding manually would still be wrong, because TIdHTTP sets up its own Accept-Encoding header if a decompressor is assigned). I commented on that in the other question you linked to. So TIdHTTP ends up returning the raw gzip bytes without decoding them, and then TRESTClient converts them as-is to a charset-decoded UnicodeString (since you are reading the Content property). That is why you are seeing the bytes getting messed up.

You need to get rid of the AcceptEncoding assignment.

Why does this happen?

Because TRestClient does not assign a gzip decompressor to its internal TIdHTTP object, but you are tricking the server into thinking it did.

should automatically decode the gzip stream when you set AcceptEncoding to 'gzip, deflate'

No, because there is no decompressor assigned.

Update: that being said, I would probably just drop TRESTClient and use TIdHTTP directly. The following works for me when I try it:

var
  HTTP: TIdHTTP;
  JSON: string;
begin
  HTTP := TIdHTTP.Create;
  try
    HTTP.Compressor := TIdCompressorZLib.Create(HTTP);
    // starting with SVN rev 5224, the TIdHTTP.IOHandler property no longer
    // needs to be explicitly set in order to request HTTPS urls.  TIdHTTP
    // now creates a default SSLIOHandler internally if needed.  But if you
    // are using an older release, you will have to assign the IOHandler... 
    //
    // HTTP.IOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(HTTP);
    //
    JSON := HTTP.Get('https://api.stackexchange.com/2.2/users/511529?site=stackoverflow');
  finally
    Http.Free;
  end;
  ShowMessage(JSON);
end;

Displays:

{"items":[{"badge_counts":{"bronze":96,"silver":53,"gold":4},"account_id":240984,"is_employee":false,"last_modified_date":1419235802,"last_access_date":1419293282,"reputation_change_year":15259,"reputation_change_quarter":2983,"reputation_change_month":1301,"reputation_change_week":123,"reputation_change_day":0,"reputation":61014,"creation_date":1290042241,"user_type":"registered","user_id":511529,"accept_rate":100,"location":"Netherlands","website_url":"http://www.eftepedia.nl","link":"https://stackoverflow.com/users/511529/goleztrol","display_name":"GolezTrol","profile_image":"https://www.gravatar.com/avatar/b07c67edfcc5d1496365503712de5c2a?s=128&d=identicon&r=PG"}],"has_more":false,"quota_max":300,"quota_remaining":295}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!