【问题标题】:Problem with content range with Indy TIdHttp put command against MS Graph API针对 MS Graph API 的 Indy TIdHttp put 命令的内容范围问题
【发布时间】:2021-11-30 03:53:42
【问题描述】:

我正在尝试将TIdHttp.Put() 用于Microsoft 的Graph API,但无法使用Content-Range 标头。如果我使用Ranges 属性,则会收到有关缺少Content-Range 的错误,如果我在CustomHeaders 属性中使用此标头,则会收到有关无效Content-Range 的错误。

代码如下:

sUploadSession := jsnUSession.Get('uploadUrl').JsonValue.Value;
Form1.htp2.Request.ContentType := 'application/octet-stream';
Form1.htp2.Request.ContentLength := iSize;          //  3820753
Form1.htp2.Request.CustomHeaders.Clear;
//Form1.htp.Request.CustomHeaders.Add('Content-Length: ' + IntToStr(iSize));
Form1.htp2.Request.CustomHeaders.Add('Content-Range: bytes 0-' + IntToStr(iSize - 1) + '/' + IntToStr(iSize));   //  'Content-Range: bytes 0-3820750/3820751'
{with Form1.htp2.Request.Ranges.Add do
begin
  StartPos := 0;
  EndPos := iSize;
  s := Text; 
end;   }
fs := TStringStream.Create('{' + TEncoding.Default.GetString(TFile.ReadAllBytes(sFile)) + '}');
bLog := True;
try
  Form1.htp2.Put(sUploadSession, fs);
except
  on E: EIdHTTPProtocolException do
    Form1.RichEdit1.Lines.Add(E.Message + #13#10 + E.ErrorMessage);
end;

我使用Ranges 属性时的消息是:

HTTP/1.1 400 Bad Request
{"error":{"code":"MissingContentRangeHeader","message":"Content-Range header is required."}}

CustomHeaders 中带有Content-Range 的消息是:

HTTP/1.1 400 Bad Request
{"error":{"code":"InvalidContentRangeHeader","message":"Invalid Content-Range header."}}

Indy 中的PUT 命令是否与 HTTP 中的标准兼容,或者是否需要进行调整才能使其正常工作?

【问题讨论】:

    标签: delphi microsoft-graph-api indy idhttp


    【解决方案1】:

    RangeContent-Range HTTP 标头是两个完全不同的东西。见Difference between Content-Range and Range headers?

    Content-Range 用于指定包含Content-Range 标头的同一消息正文中的字节范围。

    Range 标头用于请求来自服务器的一系列字节。响应消息将通过其自己的Content-Range 指示响应正文中发送的实际范围。

    所以,这就解释了为什么在使用 TIdHTTP.Ranges 属性时会收到“缺少 Content-Range”错误。该属性根本不适合您使用它的目的。

    至于使用TIdHTTP.Request.CustomHeaders属性发送Content-Range标头,这是正确的方法(技术上,TIdEntityHeaderInfoContentRange...属性,但它们目前只有TIdHTTP.Response使用,不是TIdHTTP.Request - that needs to be fixed)。

    您的自定义 Content-Range 标头的问题在于服务器只是将其视为错误而拒绝。这很可能意味着您使用的 iSize 值实际上与您实际发送的字节数不匹配。

    试试类似的方法:

    sUploadSession := jsnUSession.Get('uploadUrl').JsonValue.Value;
    fs := TStringStream.Create('{' + TEncoding.Default.GetString(TFile.ReadAllBytes(sFile)) + '}');
    try
      bLog := True;
      try
        iSize := fs.Size; // <-- the TStringStream constructor internally converted the String to TBytes...
        Form1.htp2.Request.ContentType := 'application/octet-stream';
        Form1.htp2.Request.ContentLength := iSize;
        Form1.htp2.Request.CustomHeaders.Clear;
        Form1.htp2.Request.CustomHeaders.Add('Content-Range: bytes 0-' + IntToStr(iSize - 1) + '/' + IntToStr(iSize));
    
        Form1.htp2.Put(sUploadSession, fs);
      except
        on E: EIdHTTPProtocolException do
          Form1.RichEdit1.Lines.Add(E.Message + #13#10 + E.ErrorMessage);
      end;
    finally
      fs.Free;
    end;
    

    但是,没有充分的理由将文件读入解码的 UTF-16 string 只是为了将其转换回字节进行传输,因此只需按原样发送原始文件字节,例如:

    sUploadSession := jsnUSession.Get('uploadUrl').JsonValue.Value;
    fs := TFileStream.Create(sFile, fmOpenRead or fmShareDenyWrite);
    try
      bLog := True;
      try
        iSize := fs.Size;
        Form1.htp2.Request.ContentType := 'application/octet-stream';
        Form1.htp2.Request.ContentLength := iSize;
        Form1.htp2.Request.CustomHeaders.Clear;
        Form1.htp2.Request.CustomHeaders.Add('Content-Range: bytes 0-' + IntToStr(iSize - 1) + '/' + IntToStr(iSize));
    
        Form1.htp2.Put(sUploadSession, fs);
      except
        on E: EIdHTTPProtocolException do
          Form1.RichEdit1.Lines.Add(E.Message + #13#10 + E.ErrorMessage);
      end;
    finally
      fs.Free;
    end;
    

    或者:

    sUploadSession := jsnUSession.Get('uploadUrl').JsonValue.Value;
    fs := TBytesStream.Create(TFile.ReadAllBytes(sFile));
    try
      bLog := True;
      try
        iSize := fs.Size;
        Form1.htp2.Request.ContentType := 'application/octet-stream';
        Form1.htp2.Request.ContentLength := iSize;
        Form1.htp2.Request.CustomHeaders.Clear;
        Form1.htp2.Request.CustomHeaders.Add('Content-Range: bytes 0-' + IntToStr(iSize - 1) + '/' + IntToStr(iSize));
    
        Form1.htp2.Put(sUploadSession, fs);
      except
        on E: EIdHTTPProtocolException do
          Form1.RichEdit1.Lines.Add(E.Message + #13#10 + E.ErrorMessage);
      end;
    finally
      fs.Free;
    end;
    

    或者:

    sUploadSession := jsnUSession.Get('uploadUrl').JsonValue.Value;
    fs := TMemoryStream.Create;
    try
      bLog := True;
      try
        fs.LoadFromFile(sFile);
        fs.Position := 0;
        iSize := fs.Size;
        Form1.htp2.Request.ContentType := 'application/octet-stream';
        Form1.htp2.Request.ContentLength := iSize;
        Form1.htp2.Request.CustomHeaders.Clear;
        Form1.htp2.Request.CustomHeaders.Add('Content-Range: bytes 0-' + IntToStr(iSize - 1) + '/' + IntToStr(iSize));
    
        Form1.htp2.Put(sUploadSession, fs);
      except
        on E: EIdHTTPProtocolException do
          Form1.RichEdit1.Lines.Add(E.Message + #13#10 + E.ErrorMessage);
      end;
    finally
      fs.Free;
    end;
    

    无论哪种方式,请注意 Microsoft 建议在使用此 PUT API 时一次上传的文件不要超过 4MB。您的示例文件为 3.6MB,因此它只适合单个 PUT 请求,但要知道对于较大的文件,您将不得不将它们分成多个 4MB 上传,注意 NextExpectedRanges 字段在每个成功的响应中。

    【讨论】:

    • 它有效。我使用了 TMemoryStream 替代方案,因为它应该是最有效且可以解决问题的。谢谢雷米..
    • 就个人而言,我会改用TFileStream,以 4MB 块读取流并上传每个块,直到流用完。这样,可以上传大文件而不会浪费大量内存。
    猜你喜欢
    • 1970-01-01
    • 2022-01-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-01-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多