【发布时间】:2015-02-20 20:04:48
【问题描述】:
我试图阅读一个 REST API,它是 gzip 编码的。确切地说,我尝试阅读 StackExchange API。
我已经找到了问题Automatically Decode GZIP In TRESTResponse?,但由于某种原因,该答案并不能解决我的问题。
测试设置
在 XE5 中,我添加了具有以下相关属性的 TResClient、TResRequest 和 TResResponse。我设置了客户端的 BaseURL、请求的资源和参数,并将请求的 AcceptEncoding 设置为 gzip, deflate,这应该可以让它自动解码 gzip 后的响应。
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
这会导致网址:
https://api.stackexchange.com/2.2/users/511529?site=stackoverflow
我这样调用请求,用两个消息框显示请求的 url 和结果:
ShowMessage(RESTRequest1.GetFullRequestURL());
RESTRequest1.Execute; // Actual call
ShowMessage(RESTResponse1.Content);
如果我在浏览器中调用该 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 字符)标记。
尝试(肤浅)
我做了很多实验,比如
- 使用 RESTResponse.RawBytes 并尝试对其进行解码。我注意到这个字节数组中的字节已经无效。 TRESTResponse 源代码中的评论告诉我,“RawBytes”已经被解码,所以这是有道理的。
- 将 RESTResponse.RawBytes 保存在一个文件中,并尝试使用 7zip 和几个在线 gzip 解压缩器对其进行解压缩。当然,它们都失败了,因为即使 gzip 标头也不正确。
- 为 TRESTClient.AcceptEncoding、TRESTResponse.AcceptEncoding 和它们的组合分配了值“gzip, deflate”。还尝试将其附加到每个组件的预填充 Accept 属性中。
- 从经过身份验证的请求切换到未经身份验证的请求。我让整个 oAuth 部分工作,但我认为这会使问题变得过于复杂。不过,我在这个问题中使用的匿名 API 也有同样的问题。
不幸的是,它仍然不起作用,我仍然收到错误的响应。
尝试(深入 VCL)
最终,我更深入地研究了 TRESTRequest.Execute。我不会在这里粘贴所有代码,但最终它会通过调用来执行请求
FClient.HTTPClient.Get(LURL, LResponseStream);
FClient 是链接到请求的 TRESTClient,LResponseStream 是 TMemoryStream。我在手表中添加了LResponseStream.SaveToFile('...'),所以它会保存这个未处理的结果,等等,它给了我一个有效的 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;
根据此块上方的注释,这样做是因为内存流的内容“未根据可能存在的 Encoding 或 Content-Type Charset 参数进行编码”,这被认为是 Indy 中的错误这个 VCL 代码。
所以基本上,这里发生了什么:原始响应被视为字符串并转换为“正确”编码。 FClient.HTTPClient.Response.CharSet 是 'UTF-8',确实是 JSON 的编码,可惜这种转换只能在解压流后进行,目前还没有。所以这被我认为是一个错误。 ;)
我试图深入挖掘,但找不到应该进行减压的地方。实际的请求是由一个 IIPHTTP 实例执行的,它是 IPPeerAPI.dcu,我没有它的来源。
那么...
所以我的问题是双重的:
- 为什么会发生这种情况?当您将 AcceptEncoding 设置为 'gzip, deflate' 时,TRestClient 应自动解码 gzip 流。我错过了什么设置?还是在 XE5 中还不支持?
- 如何防止 gzip 流的这种错误翻译?我不介意自己解码响应,只要它有效,尽管理想情况下 REST 组件应该自动完成。
我的设置:VCL Forms 应用程序、Windows 8.1、Delphi XE5 Professional Update 2。
更新
- 找到了解决方法(请参阅我的回答)
- 错误报告RSP-9855 已提交质量中心
- 据说它已在 Delphi 10.1(柏林)中修复,但我尚未对此进行测试。
【问题讨论】:
标签: delphi rest utf-8 gzip delphi-xe5