Gol*_*rol 6 delphi rest gzip utf-8 delphi-xe5
我试图读取一个REST API,它是gzip编码的.确切地说,我试图阅读StackExchange API.
我已经在TRESTResponse中找到了自动解码GZIP的问题?但由于某些原因,这个答案并没有解决我的问题.
测试设置
在XE5中,我添加了TRestClient,TRestRequest和具有以下相关属性的TRestResponse.我设置了客户端的BaseURL,请求的资源和参数,以及我AcceptEncoding
对请求的设置gzip, 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
Run Code Online (Sandbox Code Playgroud)
这导致url:
https://api.stackexchange.com/2.2/users/511529?site=stackoverflow
我调用这样的请求,有两个消息框来显示url和请求的结果:
ShowMessage(RESTRequest1.GetFullRequestURL());
RESTRequest1.Execute; // Actual call
ShowMessage(RESTResponse1.Content);
Run Code Online (Sandbox Code Playgroud)
如果我在浏览器中调用该URL,我会得到一个正确的结果,这是一个json对象,其中包含我的一些用户信息.
问题
但是,在Delphi中,我没有得到JSON响应.实际上,我得到了一堆字节,这似乎是一个错误的gzip响应.我尝试用它解压缩TIdCompressorZlib.DecompressGZipStream()
,但它失败了ZLib Error (-3)
.当我自己检查响应的字节时,我看到它从#1F#3F#08开始.这特别奇怪,因为gzip标题应该是#1F#8B#08,所以#8B转换为#3F,这是一个问号.
所以在我看来,RESTClient试图将gzip流解码为好像是UTF-8响应,并且用问号替换了无效序列(#8B本身不是有效的UTF-8字符).
尝试(表面)
我做了很多实验,比如
不幸的是它仍然无效,我仍然得到了错误的回应.
尝试(深入VCL)
最后,我挖了一点,然后潜入TRestRequest.Execute.我不会在这里粘贴所有代码,但最终它会通过调用来执行请求
FClient.HTTPClient.Get(LURL, LResponseStream);
Run Code Online (Sandbox Code Playgroud)
FClient是链接到请求的TRESTClient,LResponseStream是TMemoryStream.我添加LResponseStream.SaveToFile('...')
到手表,所以它会保存这个未经处理的结果,etvoilá,它给了我一个有效的gz文件,我可以解压缩得到我的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;
Run Code Online (Sandbox Code Playgroud)
根据此块上面的注释,这样做是因为内存流的内容"未根据可能存在的Encoding或Content-Type Charset参数进行编码",这被VCL代码的编写者认为是Indy中的错误.
所以基本上,这里发生了什么:原始响应被视为一个字符串并转换为'正确'编码.FClient.HTTPClient.Response.CharSet是'UTF-8',它确实是JSON的编码,但不幸的是,这种转换只能在解压缩流之后才能完成,但尚未完成.所以我认为这是一个错误.;)
我试图深入挖掘,但我找不到应该进行减压的地方.实际请求由IIPHTTP实例执行,该实例是IPPeerAPI.dcu,我没有源代码.
所以...
所以我的问题是双重的:
我的设置:VCL Forms应用程序,Windows 8.1,Delphi XE5专业版更新2.
更新
Remy Lebeau 在回答这个问题时的输入以及他对问题在 TRESTResponse 中自动解码 GZIP?让我走上正轨。
就像他说的,设置 AcceptEncoding 是不够的,因为执行实际请求的 TIdHTTP 没有附加解压缩器,因此它无法解压缩 gzip 响应。基于稀疏的资源,我想到设置 AcceptEncoding 也会自动解压缩响应,但这个想法是错误的。
不过,在这种情况下,将 AcceptEncoding 留空也不起作用,因为这涉及的 API(即 StackExchange API)始终是压缩的,无论您是否指定接受 gzip。
因此,a) 始终压缩的响应,b) 无法解压缩的 HTTP 客户端,以及 c) 错误地假设响应已经正确解压缩的 TRESTRequest 对象组合在一起,导致了这种情况。
我只看到两个解决方案,第一个是完全放弃 TRESTClient 并仅使用普通的 TIdHTTP 执行请求。遗憾的是,因为我的目标是探索新 REST 组件的可能性,看看它们如何让生活变得更轻松。
因此另一个解决方案是为内部使用的 TIdHTTP 分配一个压缩器。
我成功了,尽管不幸的是它取消了 TREST 组件试图引入的许多抽象。这是解决这个问题的代码:
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);
Run Code Online (Sandbox Code Playgroud)
之后,我可以使用 RESTRequest1 组件成功获取 JSON 响应(至少作为文本)。
归档时间: |
|
查看次数: |
2693 次 |
最近记录: |