Совместим ли TIdHTTPServer с Microsoft BITS

Мы пытаемся написать сервер обновлений для нашего программного обеспечения, используя компонент TIdHTTPServer. В настоящее время мы обслуживаем файл XML, в котором перечислены доступные обновления и их версии файлов и т.д., Когда клиентская программа находит обновленную версию, она должна начать ее загружать с помощью BITS.

Теперь у нас есть проблема, наши программы запрашивают XML файл и видят, что доступно обновление. Затем он создает задание BITS для его загрузки, однако BITS продолжает сообщать, что загрузка не удалась. Мы можем загрузить файл с использованием того же URL-адреса и IE/Firefox/Chrome.

так что мой вопрос:

Совместим ли TIdHTTPServer с BITS?

Я спрашиваю об этом, поскольку я обнаружил, что есть требования к загрузке битов для работы.
Требования HTTP для загрузки BITS

BITS поддерживает загрузку и загрузку HTTP и HTTPS и требует, чтобы сервер поддерживал протокол HTTP/1.1. Для загрузки метод Header HTTP-сервера должен возвращать размер файла, а метод Get должен поддерживать заголовки Content-Range и Content-Length. В результате BITS передает только содержимое статического файла и генерирует ошибку, если вы пытаетесь передать динамический контент, если ASP, ISAPI или CGI script не поддерживают заголовки Content-Range и Content-Length.

BITS может использовать сервер HTTP/1.0, если он удовлетворяет требованиям метода Head и Get.

Чтобы поддерживать загрузку диапазонов файла, сервер должен поддерживать следующие требования:

Разрешить заголовкам MIME включать стандартные заголовки Content-Range и Content-Type плюс максимум 180 байтов других заголовков. Разрешить максимум два CR/LF между заголовками HTTP и первой граничной строкой.

Ответ 1

Итак, ответ на этот вопрос:

Да TIdHTTPServer совместим с битами.

Но только если вы готовы самостоятельно выполнить эту работу.

Как предложили @Rob Kennedy и Myself, можно прочитать заголовки и отправить данные обратно с использованием запрошенных диапазонов, по одному фрагменту за раз.

Вот пример того, что я делаю в событии 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. Самое главное, что это работает.

Любые комментарии к коду будут оценены, конструктивная критика всегда приветствуется.

Ответ 2

Просто нашел ошибку в indy, которая предотвращает передачу файлов более 2,1 ГБ при использовании запросов диапазона.

здесь

IdHTTPHeaderInfo.pas aprox line 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 для исправления

Ответ 3

Когда вы обрабатываете событие OnCommandGet, вам предоставляется TIdRequestHeaderInfo, который спускается от TIdEntityHeaderInfo; который содержит все заголовки, содержащиеся в запросе, и даже анализирует некоторые значения заголовков для чтения как свойства, включая ContentRangeStart, ContentRangeEnd и ContentLength.

Эти свойства можно использовать для заполнения потока, который вы присваиваете свойству TIdHTTPResponseInfo.ContentStream. Весь поток будет отправлен.

Это ваша работа, чтобы различать запросы GET и HEAD; OnCommandGet будет срабатывать в любом случае. Проверьте свойство IdHTTPRequestInfo.CommandType.

Итак, хотя Indy может не поддерживать BITS, он предоставляет все инструменты, необходимые для написания программы, поддерживающей BITS.