Indy TIdHttp put 命令针对 MS Graph 的内容范围存在问题 API

Problem with content range with Indy TIdHttp put command against MS Graph API

我正在尝试将 TIdHttp.Put() 用于 Microsoft 的 Graph API,但无法使用 Content-Range header。如果我使用 Ranges 属性 然后我得到一个关于缺少 Content-Range 的错误,如果我在 CustomHeaders 属性 中使用这个 header然后我收到关于无效 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."}}

CustomHeadersContent-Range的留言是:

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

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

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

Content-Range 用于指定包含 Content-Range header.

的同一消息的 body 中的字节范围

Rangeheader用于请求来自服务器的字节范围。响应消息将通过它自己的 Content-Range 指示在响应中发送的实际范围 body.

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

至于使用 TIdHTTP.Request.CustomHeaders 属性 发送 Content-Range header,这是正确的方法(从技术上讲,TIdEntityHeaderInfoContentRange... 属性,但它们目前仅被 TIdHTTP.Response 使用,而不被 TIdHTTP.Request - that needs to be fixed).

使用

您的自定义 Content-Range header 的问题在于服务器只是将其视为错误而拒绝。这很可能意味着您使用的 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 只是为了将其转换回字节以进行传输,因此只需发送原始文件字节 as-is ,例如:

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 每个成功响应中的字段。