【问题标题】:TidHttp file download trows Out of memory ExceptionTidHttp 文件下载出现内存不足异常
【发布时间】:2012-05-23 08:25:15
【问题描述】:

考虑以下使用 Indy 组件从 Internet 下载文件的代码:

procedure TForm26.Button1Click(Sender: TObject);
var
  vFileStream : TStream;
begin
  DeleteFile('C:\test.exe');
  vFileStream := TFileStream.Create('C:\test.exe', fmCreate);
  IdHTTP1.Get('SomeUrl', vFileStream);
  vFileStream.Free;
end;

我收到内存不足异常。发生的情况是,除了我使用TFileStream 之外,写入它的字节不会直接进入磁盘,而是会一直保留在内存中,直到get 终止。

我要下载的文件非常非常大。

有谁知道如何下载大文件而不会出现内存不足异常?

Delphi 2010 和来自 Indy 的 SVN 的最新 Indy 10。

编辑

这不是FileStream 的问题。这是一个Indy 问题。 Indy 以某种方式在将文件写入流之前将其缓存在内存中。

【问题讨论】:

  • 如果重要的话,什么版本的Delphi和什么版本的Indy?
  • Delphi 2010 和来自 Indy 的 SVN 的最新 Indy 10。
  • 代码告诉你什么? IdHTTP1.Get 是否在写入文件之前将整个文件下载到内存中?
  • @DavidHeffernan 我不知道,我没有看过 indys 代码。
  • TIdHTTP 如果数据被压缩(Indy 还不支持 HTTP 的流式解压),或者数据是 HTML 并且TIdHTTP.HTTPOptions 属性不包含hoNoParseMetaHTTPEquiv 标志。

标签: delphi file download delphi-2010 indy


【解决方案1】:

TIdHTTP 如果数据被压缩,或者数据是 HTML 并且TIdHTTP.HTTPOptions 属性不包含hoNoParseMetaHTTPEquiv 标志,则将整个文件下载到内存中。

Indy 还不支持 HTTP 的流式解压(不过它支持 FTP),因此TIdHTTP 将整个压缩数据缓存在内存中,然后再将其解压到文件中。

在 HTML 通过 HTML <meta> 标记用新值覆盖 HTTP 标头值的情况下,有时需要解析 HTML,最重要的是数据的 Charset 值,因此 TIdHTTP 可以在数据为以String 的形式返回给用户的代码。启用 hoNoParseMetaHTTPEquiv 标志会禁用该解析,从而禁用任何 HTML 数据缓存(除非也使用压缩)。

【讨论】:

  • 有什么解决方法吗?肯定有,因为这似乎有点限制。
  • 为了首先处理压缩数据,您必须将TIdZLibCompressorBase-派生组件分配给TIdHTTP.Compressor 属性。这反过来让TIdHTTP 自动通知服务器支持压缩响应(除非您使用TIdHTTP.Request.AcceptEncodings 属性手动覆盖它)。因此,解决方法是简单地删除/禁用 Compressor,如果事实证明这是实际问题。我在 Indy 的问题跟踪器中添加了票证,以支持未来版本中的流式解压。
  • 我记得在某处看到 Netscape 或 Internet Explorer 过去总是下​​载 HTML 文档的 first 行,如果它包含可怕的 charset 元标记,请重新加载使用新字符集的整个页面。似乎有点相关。
  • 所以,总而言之,答案是不使用压缩来避免将整个内容存储在内存中?
  • @RemyLebeau 请检查我的答案。
【解决方案2】:

我发现了问题。我在服务器端使用了 Indys ServeFile 函数。

此函数检查是否指定了Content-Type,如果没有指定,它会自动检测Content-Type。问题是我没有更改Content-Type,默认情况下它是text/html。更改内容类型使客户端直接写入流。

我认为serveFile函数应该始终设置正确的Content-Type以避免此类问题。

在客户端,我发现这段代码对我有很大帮助:

  LParseHTML := IsContentTypeHtml(AResponse) and Assigned(AResponse.ContentStream) and not (hoNoParseMetaHTTPEquiv in FOptions);
  LCreateTmpContent := LParseHTML and not (AResponse.ContentStream is TCustomMemoryStream);

【讨论】:

  • 我无法使用 Indy 10 的最新 SVN 快照重现这一点。当OnCommandGet 事件被触发时,还没有分配默认的ContentType,所以ServeFile() 应该分配任何东西ContentType 属于文件的扩展名。唯一一次 TIdHTTPServer 分配 'text/html' 的默认 ContentType 是在 TIdResponseHeaderInfo 构造函数中,但 TIdHTTPResponseInfo 构造函数之后会将其重置为空白字符串。
  • 没关系,毕竟我能够重现它。好像是随机的,有时候默认的ContentType是空白的,有时候是text/html。我会继续调试的。
  • @RemyLebeau 你认为这是一个错误吗?
  • 忽略我最后的评论。当我看到空白值时,我不小心看到了TIdHTTPRequestInfo 而不是TIdHTTPResponseInfo。所以是的,response 的默认值始终是 text/html,所以我会更新 ServeFile() 来解决这个问题。
  • @RafealColucci:不,这不是ServeFile() 中的错误。 TIdHTTPServer 允许用户在调用ServeFile() 之前分配自定义ContentType。只是TIdHTTPServer 本身在设置自己的非空白默认值,所以ServeFile() 不知道有什么区别。我现在已经删除了非空白默认值(并更新了WriteHeader() 以解决这个问题,以便它可以保持与期望将默认值发送给客户端的用户代码的向后兼容性)。
【解决方案3】:

您是否尝试通过设置Request.ContentRangeStartRequest.ContentRangeEnd 来分段下载文件?

【讨论】:

  • 我不知道该怎么做。
  • @RafaelColucci - 这里 delphigroups.info/2/5/211924.html 是 Remy Lebeau (TeamB) 的示例
  • @Pol 我知道这可能是一个解决方案,但这不是我需要的。如果我采用您的解决方案,我将不得不对我的代码进行大量更改,而且我无法相信 indys 不会直接写入流。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-11
  • 1970-01-01
  • 2011-06-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多