2014-12-22 5 views
5

gzip으로 인코딩 된 REST API를 읽으려고했습니다. 정확히 말하면 StackExchange API를 읽으려고했습니다.TRestClient/TRestRequest가 gzip 응답을 잘못 디코딩합니다.

나는 이미 Automatically Decode GZIP In TRESTResponse?이라는 질문을 발견했지만, 그 대답은 어떤 이유로 든 내 문제를 해결하지 못합니다.

테스트 설정

는 XE5에서, 나는 TRestClient하는 TRestRequest 다음과 같은 관련 특성을 가진 TRestResponse을 추가했다. 요청의 클라이언트, 리소스 및 매개 변수의 BaseURL을 설정하고 요청의 AcceptEncodinggzip, deflate으로 설정하여 gzipped 응답을 자동으로 해독해야합니다.

object RESTClient1: TRESTClient 
    BaseURL = 'https://api.stackexchange.com/2.2' 
    end 
    object RESTRequest1: TRESTRequest 
    AcceptEncoding = 'gzip, deflate' 
    Client = RESTClient1 
    Params = < 
     item 
     Kind = pkURLSEGMENT 
     name = 'id' 
     Options = [poAutoCreated] 
     Value = '511529' 
     end 
     item 
     name = 'site' 
     Value = 'stackoverflow' 
     end> 
    Resource = 'users/{id}' 
    Response = RESTResponse1 
    end 
    object RESTResponse1: TRESTResponse 
    end 

이 URL에 결과 :

: URL과 요청의 결과를 보여주기 위해 두 개의 메시지 상자,

https://api.stackexchange.com/2.2/users/511529?site=stackoverflow

는이 같은 요청을 호출

ShowMessage(RESTRequest1.GetFullRequestURL()); 
RESTRequest1.Execute; // Actual call 
ShowMessage(RESTResponse1.Content); 

브라우저에서 해당 URL을 호출하면 적절한 r 내 사용자 정보가있는 json 객체 인 esult.

문제

그러나, 델파이, 나는 JSON 응답을하지 않습니다. 사실 으로 보이는 바이트 묶음이 mangled gzip 응답으로 표시됩니다. TIdCompressorZlib.DecompressGZipStream()으로 압축을 풀려고했으나 ZLib Error (-3)으로 실패합니다. 응답의 바이트를 직접 조사하면 # 1F # 3F # 08로 시작하는 것을 볼 수 있습니다. 이것은 gzip 헤더가 # 1F # 8B # 08이어야하므로 # 8B가 # 3F로 바뀌므로 특히 이상합니다. 이것은 물음표입니다.

그래서 RESTClient가 gzip 스트림을 UTF-8 응답처럼 디코드하려고 시도하고 잘못된 시퀀스 (# 8B는 유효한 UTF-8 문자가 아님)를 대체했습니다. 물음표. 내가

  • 사용 RESTResponse.RawBytes처럼, 꽤 번의 실험을 수행하고 디코딩하려고했습니다

    시도 (표면). 이 바이트 배열의 바이트가 이미 유효하지 않음을 알았습니다. TRESTResponse의 소스에있는 의견은 'RawBytes'가 이미 디코딩되었으므로 의미가 있습니다.

  • 파일에 RESTResponse.RawBytes를 저장하고 7zip과 몇 개의 온라인 gzip 압축 풀기 프로그램으로 압축을 풀려고했습니다. 물론 gzip 헤더도 올바르지 않기 때문에 모두 실패했습니다.
  • 'gzip, deflate'값을 TRESTClient.AcceptEncoding, TRESTResponse.AcceptEncoding 및 이들의 조합에 할당했습니다. 또한 각 구성 요소의 미리 채워진 Accept 속성에 추가하려고했습니다.
  • 인증에서 인증되지 않은 요청으로 전환되었습니다. 나는 전체 oAuth 부분을 작동 시켰지 만, 그렇게하면 질문이 너무 복잡해집니다.하지만이 질문에서 사용했던 익명의 API에는 동일한 문제가 있습니다.

불행히도 여전히 작동하지 않으며 난 여전히 응답이 엉망입니다. (VCL을 파고)

시도한다는

결국, 나는 TRestRequest.Execute에 좀 더 깊이, 그리고 비둘기를 파고. 나는 여기에 모든 코드를 붙여 넣을 수 없습니다하지만, 결국은

FClient.HTTPClient.Get(LURL, LResponseStream); 

FClient를 호출하여 요청을 수행 요청에 연결되어있는 TRESTClient하고 LResponseStream은 TMemoryStream이있다. 시계에 LResponseStream.SaveToFile('...')을 추가 했으므로이 처리되지 않은 결과를 저장합니다. et voilá, 유효한 JSF 파일을 얻었습니다.이 파일을 압축 해제하여 JSON을 얻을 수 있습니다.

해결 방법의 버그? 메모리 스트림의 내용이 "인코딩하지 않기 때문에이 블록 위의 코멘트에,이 작업을 수행 따르면

if FClient.HTTPClient.Response.CharSet > '' then 
    begin 
    LResponseStream.Position := 0; 
    S := FClient.HTTPClient.ReadStringAsCharset(LResponseStream, FClient.HTTPClient.Response.CharSet); 
    LResponseStream.Free; 
    LResponseStream := TStringStream.Create(S); 
    end; 

:

그러나 한 다음, 아래 라인의 몇 가지, 나는이 코드 조각을 참조 따라서이 VCL 코드의 작성자가 Indy의 버그로 간주하는 가능한 Encoding 또는 Content-Type Charset 매개 변수에 따라 달라집니다.

그래서 기본적으로 원시 응답은 문자열로 처리되어 '올바른'인코딩으로 변환됩니다. FClient.HTTPClient.Response.CharSet은 실제로 JSON의 인코딩 인 'UTF-8'이지만, 아직 완료되지 않은 스트림의 압축을 푼 후에 만이 변환을 수행해야합니다. 그래서 이것은 나에 의해 버그로 간주됩니다. ;)

나는 더 깊게 파고 들려고했지만이 감압이 일어난 장소를 찾을 수 없었다. 실제 요청은 소스가없는 IPPeerAPI.dcu 인 IIPHTTP 인스턴스에 의해 수행됩니다. 그래서

...

그래서 제 질문은 두 가지이다 :

  1. 왜 이런 일이 무엇입니까? AcceptEncoding을 'gzip, deflate'로 설정하면 TRestClient가 자동으로 gzip 스트림을 디코딩해야합니다. 어떤 설정을 놓쳤습니까? 또는 XE5에서 아직 지원되지 않습니까?
  2. gzip 스트림의이 잘못된 번역을 어떻게 방지합니까? 이상적으로는 REST 구성 요소가 자동으로 처리해야한다고는해도 응답을 직접 해독해도 상관 없습니다.

내 설정 : VCL 애플리케이션, 윈도우 8.1, 델파이 XE5 전문 업데이트 2.

업데이트 양식

  • 해결 방법 (내 대답을 참조) 발견
  • 버그 보고서 RSP-9855 품질 중앙 출원
  • Delphi 10.1 (Berlin)에서 수정되었지만 아직 테스트하지 않았습니다.=

답변

4
이 질문에 대한 그의 대답은 물론 질문 Automatically Decode GZIP In TRESTResponse?의 대답에 자신의 의견에

레미 Lebeau의 입력에 나를 넣어 바른 길.

실제 요청을 수행하는 TIdHTTP에는 압축 해제자가 없으므로 AcceptEncoding을 설정하는 것만으로는 충분하지 않으므로 gzip 응답을 압축 해제 할 수 없습니다. 스파 스 리소스를 기반으로 AcceptEncoding을 설정하면 응답도 자동으로 압축이 풀리 겠지만 아이디어가 잘못되었다는 생각이 들었습니다.

그래도 AcceptEncoding을 비워두면이 경우 API가 모두 StackExchange API이므로 gzip을 수락하도록 지정했는지 여부에 관계없이 always compressed이되므로이 경우에도 작동하지 않습니다.

a) 항상 압축 된 응답, b) 압축 해제 할 수없는 HTTP 클라이언트 및 c) 응답이 이미 올바르게 압축 해제 된 것으로 추정되는 TRESTRequest 객체가이 상황을 초래합니다.

두 가지 해결책이 있습니다. 첫 번째는 TRESTClient를 모두 삭제하고 일반 TIdHTTP로 요청을 수행하는 것입니다.내 목표가 새로운 REST 구성 요소의 가능성을 탐구하여 더 쉽게 삶을 유지할 수있는 방법을 모색하는 것이었기 때문에 안타깝습니다.

그래서 다른 해결책은 내부적으로 사용되는 TIdHTTP에 압축기를 할당하는 것입니다.

불행히도 TREST 구성 요소가 소개하려고하는 많은 추상화를 취소하지만 성공할 수있었습니다. 나는 성공적으로 (적어도 텍스트)로 JSON 응답을 가져 오기 위해 RESTRequest1 구성 요소를 사용할 수 있습니다,이 후

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); 

이 그것을 해결하는 코드입니다.

3

AcceptEncoding이 문제의 루트입니다

'gzip으로는 폐'. 응답을 gzip으로 인코딩 할 수 있도록 서버에 수동으로 알려주지 만, REST 소스 코드에서 볼 수있는 한 TIdHTTP 오브젝트는 TRESTClient에 내부적으로 gzip 압축 풀기가 할당되어 있지 않습니다 (심지어 가 있었는데 AcceptEncoding을 수동으로 할당하면 여전히 잘못된 것입니다. 왜냐하면 TIdHTTP은 압축 해제 기가 할당되어 있으면 Accept-Encoding 헤더를 설정하기 때문입니다. 나는 당신이에 연결된 other question에 그것에 댓글을 달았습니다. 따라서 TIdHTTP은 원시 gzip 바이트를 디코딩하지 않고 반환하고, TRESTClient은 문자 그대로 UnicodeString으로 변환합니다 (Content 속성을 읽었으므로). 그래서 바이트가 엉망이되는 것을 보았습니다.

AcceptEncoding 할당을 제거해야합니다.

왜 이런 일이 발생합니까?

TRestClient 때문에 내부 TIdHTTP 객체에 gzip으로 압축 해제를 할당하지 않습니다,하지만 당신은 한 생각에 서버를 속여 있습니다.

가 자동으로 할당 된 압축 해제가 없기 때문에,

없음 '수축, GZIP'당신이 AcceptEncoding 세트 gzip을 스트림을 디코딩한다.

업데이트 : 나는 아마도 TRESTClient을 방치하고 TIdHTTP을 직접 사용한다고합니다. 나를 위해 다음 작품은 내가하려고하면

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; 

표시합니다 :

이 는
{"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} 
+0

감사하지만, 불행히도 완전히 사실이 아닙니다. 어쩌면 내가 이것에 대해 너무 명확하지는 않지만 AcceptEncoding을 설정하는 것은 이미이 문제를 해결하기위한 시도였습니다. 처음에는 그 일을하지 않았고 여전히 같은 문제가있었습니다. 내가 게시 한 스 니펫은 결과 스트림 ('Content' 속성과'RawBytes' 속성'모두)을 항상 변환하려고합니다. 'encoding'이 'gzip'이라는 사실은 완전히 무시되며 결과 스트림은 항상 Response에 할당되기 전에 처리되므로 RawBytes에도 영향을 미칩니다. 처리되지 않은 실제 응답은 Execute 메서드 내에서 이미 구분됩니다. – GolezTrol

+0

'TRESTClient' 논리 버그와 유사합니다 (TIdHTTP 버그가 아닙니다). Embarcadero에 신고 했습니까? 어쨌든'AcceptEncoding'이 설정되어 있지 않다면, 서버는 인코딩되지 않은 실제 JSON을 보내야합니다. TRESTClient는 그것을'String'으로 디코딩합니다. 이것이 올바르게 디코딩되지 않으면, 지정된'charset'이 틀릴 가능성이 있습니다. 서버에서 전송중인 실제 REST 응답을 표시 할 수 있습니까? –

+0

TRESTClient 버그 인 것 같습니다. 나는 (아직) 그것을보고하지 않았고, 그것이 얼마나 유용한 지 잘 모르겠습니다. 나는 그들이 XE5에 대한 업데이트를한다고 생각하지 않는다. 하지만 문제는 새로운 버전에서도 존재할 수 있으므로 고려해 보겠습니다. – GolezTrol