【问题标题】:Is TIdHTTPServer Compatible with Microsoft BITSTIdHTTPServer 是否与 Microsoft BITS 兼容
【发布时间】:2011-05-23 10:06:48
【问题描述】:

我们正在尝试使用 TIdHTTPServer 组件为我们的软件编写更新服务器。目前我们正在提供一个 XML 文件,其中列出了可用更新及其文件版本等,当客户端程序找到更新版本时,它应该开始使用 BITS 下载它。

现在这是我们遇到问题的地方,我们的程序正在请求 XML 文件并看到有可用的更新。然后它会创建一个 BITS 作业来下载它,但是 BITS 不断报告下载失败。我们可以使用相同的 URL 和 IE/Firefox/Chrome 下载文件。

所以我的问题:

TIdHTTPServer 是否与 BITS 兼容?

我问这个问题是因为我发现 bit 需要这些下载要求才能工作。
HTTP Requirements for BITS Downloads

BITS 支持 HTTP 和 HTTPS 下载和上传,并要求服务器支持 HTTP/1.1 协议。对于下载,HTTP 服务器的 Head 方法必须返回文件大小,并且它的 Get 方法必须支持 Content-Range 和 Content-Length 标头。因此,BITS 仅传输静态文件内容,如果您尝试传输动态内容,则会生成错误,除非 ASP、ISAPI 或 CGI 脚本支持 Content-Range 和 Content-Length 标头。

BITS 可以使用 HTTP/1.0 服务器,只要它满足 Head 和 Get 方法的要求。

要支持文件的下载范围,服务器必须支持以下要求:

允许 MIME 标头包含标准的 Content-Range 和 Content-Type 标头,以及最多 180 字节的其他标头。 在 HTTP 标头和第一个边界字符串之间最多允许两个 CR/LF。

【问题讨论】:

  • 好的,查看了 wireshark 跟踪和 Indy 源代码,看起来 TIdHTTPServer 确实不支持 Range 检索请求的 Range / Content-Range 标头。
  • 那么是否可以从 GET 请求中读取 Range 标头,然后将请求的字节从源文件中读取到内存流中,将内存流分配给内容流以进行发送并最终设置内容范围标头。
  • TIdHTTPServer 支持Content-TypeContent-RangeRange 标头,但是应用程序的 OnCommandGet 事件处理程序负责实际查看它们并相应地发送正确的数据。 TIdHTTPHeaderEntityInfo 类具有可用的 ContentType、ContentRangeStart、ContentRangeEnd、ContentRangeInstanceLength 和 Range 属性。 TIdHTTPServer 本身并不处理实际的数据范围,用户的代码必须管理它。

标签: delphi http indy httpserver microsoft-bits


【解决方案1】:

刚刚在 indy 中发现了一个错误,该错误会在使用范围请求时阻止传输超过 2.1GB 的文件。

这里是

IdHTTPHeaderInfo.pas大约 770 行

procedure TIdEntityRange.SetText(const AValue: String);
var
  LValue, S: String;
begin
  LValue := Trim(AValue);
  if LValue <> '' then
  begin
    S := Fetch(LValue, '-'); {do not localize}
    if S <> '' then begin
      FStartPos := StrToIntDef(S, -1);
      FEndPos := StrToIntDef(Fetch(LValue), -1);
      FSuffixLength := -1;
    end else begin
      FStartPos := -1;
      FEndPos := -1;
      FSuffixLength := StrToIntDef(Fetch(LValue), -1);
    end;
  end else begin
    FStartPos := -1;
    FEndPos := -1;
    FSuffixLength := -1;
  end;
end;

这应该是

procedure TIdEntityRange.SetText(const AValue: String);
var
  LValue, S: String;
begin
  LValue := Trim(AValue);
  if LValue <> '' then
  begin
    S := Fetch(LValue, '-'); {do not localize}
    if S <> '' then begin
      FStartPos := StrToInt64Def(S, -1);
      FEndPos := StrToInt64Def(Fetch(LValue), -1);
      FSuffixLength := -1;
    end else begin
      FStartPos := -1;
      FEndPos := -1;
      FSuffixLength := StrToInt64Def(Fetch(LValue), -1);
    end;
  end else begin
    FStartPos := -1;
    FEndPos := -1;
    FSuffixLength := -1;
  end;
end;

Remy 修复一个

【讨论】:

    【解决方案2】:

    当你处理OnCommandGet 事件时,你会得到一个TIdRequestHeaderInfo,它是TIdEntityHeaderInfo 的后代;它包含请求包含的所有标头,它甚至解析出一些标头值以读取为属性,包括ContentRangeStartContentRangeEndContentLength

    您可以使用这些属性来填充分配给TIdHTTPResponseInfo.ContentStream 属性的流。 整个流将被发送。

    区分 GET 和 HEAD 请求是您的工作; OnCommandGet 无论如何都会被触发。检查IdHTTPRequestInfo.CommandType 属性。

    因此,尽管 Indy 可能不支持 BITS,但它提供了编写确实支持 BITS 的程序所需的所有工具。

    【讨论】:

    • 感谢您提供我们目前正在调查的信息,我将发布我们的结果。 Remy Lebeau 的任何帮助将不胜感激
    • 你在一半的地方看起来 ContentRangeStart/End 返回它们的默认值 -1。由于 BITS 发送范围 = bytes=0-1999。要获取 BITS 发送的 Range,您需要使用 TIdRequestHeaderInfo 上的 Ranges 属性。这包含请求标头中指定的范围列表。所以对于 BITS ARequestInfo.Ranges[0].StartPos;和 ARequestInfo.Ranges[0].EndPos;将为您提供所需的信息,您可能还想检查单位以查看它是否以字节为单位,在这种情况下,您需要检查 ARequestInfo.Ranges.Units
    • @MikeT:ContentRange... 属性是响应属性,而不是请求属性。接收请求时需要使用 Range 属性,发送响应时需要使用 ContentRange... 属性。
    • @Remy Lebeau - TeamB 我在完成研究并阅读了 Indy 源代码后理解了这一点。我试图向 Rob Kennedy 解释 ContentRange 用于响应而不是请求,因此在请求标头中提到的属性默认为 -1。所以你应该对请求使用 Ranges,对响应使用 ContentRangeStart/End。
    【解决方案3】:

    所以这个问题的答案是:

    是的,TIdHTTPServer 是位兼容的。

    但前提是你准备好自己动手。

    正如@Rob Kennedy 和我自己所建议的,可以读取标题并使用请求的范围将数据发送回,一次一个块。

    这是我在OnCommandGet 事件中所做的示例

    procedure TForm3.IdHTTPServer1CommandGet(AContext: TIdContext;
      ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
    var
      Ranges : TIdEntityRanges;
      DataChunk: TMemoryStream;
      ReqFile: TFileStream;
      ChunkLength: Int64;
      Directory, FileName: string;
    begin
      Directory := 'H:';
    
      case ARequestInfo.Ranges.Count of
      0:
        begin
          //serve file normally
        end;
      1:
        begin
          //serve range of bytes specified for file
    
          filename := Directory + ARequestInfo.Document;
    
          if FileExists(FileName) then
          begin
            ReqFile := TFileStream.Create(FileName, fmOpenRead);
            try
              ChunkLength := Succ(ARequestInfo.Ranges.Ranges[0].EndPos - ARequestInfo.Ranges.Ranges[0].StartPos);
    
              if ChunkLength > ReqFile.Size then
                ChunkLength := ReqFile.Size;
    
              DataChunk := TMemoryStream.Create;
              DataChunk.Posistion := ARequestInfo.Ranges.Ranges[0].StartPos;  
              DataChunk.CopyFrom(ReqFile, ChunkLength);
    
              AResponseInfo.ContentStream := DataChunk;
              AResponseInfo.ContentType := IdHTTPServer1.MIMETable.GetFileMIMEType(FileName);
              AResponseInfo.ContentRangeUnits := ARequestInfo.Ranges.Units;
              AResponseInfo.ContentRangeStart := ARequestInfo.Ranges.Ranges[0].StartPos;
              AResponseInfo.ContentRangeEnd := ARequestInfo.Ranges.Ranges[0].StartPos + Pred(ChunkLength);
              AResponseInfo.ContentRangeInstanceLength := ReqFile.Size;
              AResponseInfo.ResponseNo := 206;
            finally
              ReqFile.Free;
            end;
          end
          else
            AResponseInfo.ResponseNo := 404;
    
        end
      else
        begin
          //serve the file as multipart/byteranges
        end;
      end;
    
    end;
    

    这还没有完成,但它显示了响应来自 BITS 的范围请求的基础知识。最重要的是它有效。

    任何关于代码的 cmets 都将不胜感激,欢迎提出建设性的批评。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-09-18
      • 1970-01-01
      • 1970-01-01
      • 2017-06-07
      • 2011-10-22
      • 2017-02-12
      • 1970-01-01
      相关资源
      最近更新 更多